輔助功能 之 小米手機懸浮窗權限

輔助功能 之 小米手機懸浮窗權限

最近做項目遇到小米手機比較人(zhuang)性(bi)化的懸浮窗權限,當在小米手機上安裝完應用後默認是關閉這個權限的,需要用戶手動到應用詳情頁打開該權限。

重(keng)要(die)的是使用這個權限開關係統window後, 小米手機不給任何提示就是不給彈窗。一開始以爲是自己代碼邏輯寫錯了,半天才反應過來,小米還有個這個權限,當天6.0以上安卓系統也需要這個權限,但是會有log提示的。

這麼麻煩的操作怎麼可能讓用戶自己去找應用詳情然後開啓操作呢?本文將實現一鍵開啓小米懸浮窗權限!

1分析問題

想要實現自動調整到改應用的詳情頁的權限管理頁面,就要知道權限管理頁的類名及包名,我們又沒有小米rom的源碼,怎麼才能知道指定頁面的相關信息呢?

查看權限頁面類名

這個方法應該有很多中,但是我只驗證了一種:想到了 adb shell dumpsys activity

usb鏈接電腦後,手動打開應用的詳情頁面裏的權限管理頁面:

類名信息: com.miui.securitycenter/com.miui.permcenter.permissions.AppPermissionsEditorActivity

構造跳轉Intent

知道到了要跳轉的activity,我們直接構造Intent 是否可以直接跳過去?

答案肯定是不行的. Intent 需要構造參數,來區分指定app的權限管理頁面:

   /**
     * 經測試V5版本是有區別的
     * @param context
     */
    public void openMiuiPermissionActivity(Context context) {
        Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");

        if ("V5".equals(getProperty())) {
            PackageInfo pInfo = null;
            try {
                pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e("canking", "error");
            }
            intent.setClassName("com.miui.securitycenter", "com.miui.securitycenter.permission.AppPermissionsEditor");
            intent.putExtra("extra_package_uid", pInfo.applicationInfo.uid);
        } else {
            intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
            intent.putExtra("extra_pkgname", context.getPackageName());
        }

        if (isActivityAvailable(context, intent)) {
            if (context instanceof Activity) {
                Activity a = (Activity) context;
                a.startActivityForResult(intent, 2);
            }
        } else {
            Log.e("canking", "Intent is not available!");
        }
    }

測試適配rom

經測試V5版本和後續版本是後區別的, 分別需要app ID和pkgname. 爲了區分V5版本,我們需要得到小米rom的版本名:

    public static String getProperty() {
        String property = "null";
        if (!"Xiaomi".equals(Build.MANUFACTURER)) {
            return property;
        }
        try {
            Class<?> spClazz = Class.forName("android.os.SystemProperties");
            Method method = spClazz.getDeclaredMethod("get", String.class, String.class);
            property = (String) method.invoke(spClazz, "ro.miui.ui.version.name", null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return property;
    }

該反射方法來自網絡,經驗證是有效的.

這樣我們就跳轉到了指定應用的權限管理頁面.

2.實現一鍵打開

標題已經寫了,我們的目標是用戶一鍵開啓,入口做到一鍵就能開啓小米rom懸浮窗權限呢? 可以利用安卓輔助功能自動幫用戶跳轉, 自動點擊打開權限,完成操作後返回.

這裏寫了個BaseAccessibilityService 通用的操作方法封裝在這裏.

       /**
     * Created by changxing on 16-6-2.
     */
    public class BaseAccessService extends AccessibilityService {

        @Override
        protected void onServiceConnected() {
        super.onServiceConnected();
        }

        @Override
        public void onInterrupt() {

        }

        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {

        }

        protected boolean clickByText(AccessibilityNodeInfo nodeInfo, String str) {
        if (null != nodeInfo) {
            List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(str);
            if (null != list && list.size() > 0) {
                AccessibilityNodeInfo node = list.get(list.size() - 1);
                if (node.isClickable()) {
                    return node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                } else {
                    AccessibilityNodeInfo parentNode = node;
                    for (int i = 0; i < 5; i++) {
                        if (null != parentNode) {
                            parentNode = parentNode.getParent();
                            if (null != parentNode && parentNode.isClickable()) {
                                return parentNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                            }
                        }
                    }
                }
            }
        }
        return false;
        }

        protected AccessibilityNodeInfo findOpenButton(AccessibilityNodeInfo node) {
        if (node == null)
            return null;

        //非layout元素
        if (node.getChildCount() == 0) {
            if ("android.widget.Button".equals(node.getClassName())) {
                return node;
            } else
                return null;
        }

        //layout元素,遍歷找button
        for (int i = 0; i < node.getChildCount(); i++) {
            AccessibilityNodeInfo button = findOpenButton(node.getChild(i));
            if (button != null)
                return button;
        }
        return null;
        }
    }

繼承這個class ,重寫onAccessibilityEvent ,在該方法內處理具體邏輯:

到這裏只監聽TYPE_WINDOW_STATE_CHANGED類型就行了.通過控件的TEXT來實現找到需要點擊的控件.
這裏可以Dump View hierarchy工具來查看我們想要的控件具體信息.

Dump View hierarchy

     @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        XLogger.v("eventType:" + eventType);

        switch (eventType) {
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                String clazzName = event.getClassName().toString();
                AccessibilityNodeInfo nodeInfo = event.getSource();
                XLogger.i( "懸浮窗:" + clazzName);
                if (clazzName.equals("com.miui.permcenter.permissions.AppPermissionsEditorActivity")) {
                    if (end) {
                        clickByText(nodeInfo, "XiaomiPJ");
                    } else {
                        boolean access = clickByText(nodeInfo, "顯示懸浮窗");
                        XLogger.i("access" + access);
                    }
                }
                if (clazzName.equals("miui.app.AlertDialog")) {
                    end = clickByText(nodeInfo, "允許");
                    XLogger.i( "getClick:" + end);
                }
        }
        }

到這裏就可以實現一鍵開啓小米rom懸浮窗權限了

但是一鍵開啓前我們需要判斷,該權限是否已經開啓:

       /**
         * 判斷MIUI的懸浮窗權限
         * @param context
         * @return
         */
        @TargetApi(Build.VERSION_CODES.KITKAT)
        public static boolean isMiuiFloatWindowOpAllowed(Context context) {
        final int version = Build.VERSION.SDK_INT;
        if (version >= 19) {
            return checkOp(context, 24);  // AppOpsManager.OP_SYSTEM_ALERT_WINDOW
        } else {
            if ((context.getApplicationInfo().flags & 1 << 27) == 1 << 27) {
                return true;
            } else {
                return false;
            }
        }
        }

        @TargetApi(Build.VERSION_CODES.KITKAT)
        public static boolean checkOp(Context context, int op) {
        final int version = Build.VERSION.SDK_INT;

        if (version >= 19) {
            AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            try {

                Class<?> spClazz = Class.forName(manager.getClass().getName());
                Method method = manager.getClass().getDeclaredMethod("checkOp", int.class, int.class, String.class);
                int property = (Integer) method.invoke(manager, op,
                        Binder.getCallingUid(), context.getPackageName());
                XLogger.e(AppOpsManager.MODE_ALLOWED + " invoke " + property);

                if (AppOpsManager.MODE_ALLOWED == property) { 
                    return true;
                } else {
                    return false;
                }
            } catch (Exception e) {
                XLogger.e(e.getMessage());
            }
        } else {
            XLogger.e("Below API 19 cannot invoke!");
        }
        return false;
        }

api>=19需要用反射來活取系統相關配置信息,應該也適用於魅族手機,爲驗證.

這裏我們就實現了一鍵開啓小米Rom懸浮窗權限,並且實現了判斷是否已經開啓了該權限狀態.

個人學習博客
本Demo相關源碼地址: https://github.com/CankingApp/XiaomiPJ, 歡迎下載交流學習~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章