天天看點

小米MIUI NFC、WIFI權限排查踩坑

背景: 小米手機使用NFC時,會提示是否使用NFC彈窗,如果點選拒絕,則下次碰 NFC 無反應。

一. 現象

NFC 的使用非常簡單,隻需要在 AndroidManifest.xml 上 注冊權限即可:

<uses-permission android:name="android.permission.NFC" />
           

但 MIUI 可能考慮安全問題,NFC 有使用權限限制,如:

小米MIUI NFC、WIFI權限排查踩坑

經排查,安卓官網并沒有對 NFC 使用有特殊權限申請,其他手機使用 NFC 無該情況。

二. 問題排查

既然涉及權限問題,可能 MIUI 需要申請權限,抱着疑問使用了:

boolean isHasNfcPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.NFC)
                == PackageManager.PERMISSION_GRANTED;
           

無論關閉還是打開NFC,結果都是 PERMISSION_GRANTED ,看來這裡行不通。

權限問題,除了 ActivityCompat.checkSelfPermission() , 還有 AppOpsManager 權限管理類;

進到源碼,檢視 op 值 ,從數組來看,并發現并沒有跟 NFC 相關的權限。

小米MIUI NFC、WIFI權限排查踩坑

這個時候就有點蛋疼了。。。

三. 解決問題

後來網上發現了一遍文章,https://blog.csdn.net/GuangkuoDing/article/details/100324162 ,說是小米官網給的,“背景彈出頁面” op 值為 10021 ,但沒說 NFC 是多少。

不過可以看到改值還是比較大的,原生的數組長度為 90 ,估計 MIUI 取了個比較靠後的用來測試。

是以,我大概取了個範圍,從 10000 到 10030,進行對比測試:

for (int op = 10000; op <= 10030; op++) {
    try {
        AppOpsManager appOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
        Method checkOpNoThrow = appOpsManager.getClass().getDeclaredMethod("checkOpNoThrow",
                int.class, int.class, String.class);
        checkOpNoThrow.setAccessible(true);
        int result = (int) checkOpNoThrow.invoke(appOpsManager, op, Process.myUid(),
                getPackageName());
        Log.d(TAG, "zsr op 值: "+op+" / 結果: "+result);
    } catch (Exception e) {
        RLog.e(TAG, "hasMIUIPermission: " + e.toString());
        e.printStackTrace();
        Log.d(TAG, "zsr 無效op: "+op);
    }
}
           

控制變量為 : NFC 的開關

小米MIUI NFC、WIFI權限排查踩坑

發現有意外收獲。

得到的測試結果如下圖,可以看到 10016 是有變動: 0 → 1

小米MIUI NFC、WIFI權限排查踩坑

通過反複驗證,得到 NFC 的 op 值為 10016,WiFi 的 op 值為 10001。故 AppOpsManager 是可行的,隻是值得自己去排查。

四. 工具

知道權限,還得跳轉該目前頁面,通過指令 adb shell dumpsys activity top | grep ACTIVITY 可以拿到授權的MIUI包名為:

com.miui.securitycenter/com.miui.permcenter.permissions.PermissionsEditorActivity
           

而不同平台的MIUI 是不一樣的,是以寫了個MIUI的工具類,友善開發:

public class MiuiUtils {
    private static final String TAG = "MiuiUtils";
    private static final int OPS_NFC = 10016;
    private static final int OPS_WIFI = 10001;
    private static Context context = MaxhubApplication.getContext().getApplicationContext();
    /**
     * 跳轉到MIUI應用權限設定頁面
     *
     * @param context context
     */
    private static void jumpToPermissionsEditorActivity(Context context) {
        try {
            // MIUI 8
            Intent localIntent = new Intent();
            localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
            localIntent.putExtra("extra_pkgname", context.getPackageName());
            localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(localIntent);
        } catch (Exception e) {
            RLog.i(TAG, "jumpToPermissionsEditorActivity: "+e.toString());
            try {
                // MIUI 5/6/7
                Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR");
                localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
                localIntent.putExtra("extra_pkgname", context.getPackageName());
                localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(localIntent);
            } catch (Exception e1) {
                RLog.i(TAG, "jumpToPermissionsEditorActivity2: "+e.toString());
                // 否則跳轉到應用詳情
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                Uri uri = Uri.fromParts("package", context.getPackageName(), null);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.setData(uri);
                context.startActivity(intent);
            }
        }
    }
 
    /**
     * 檢察小米的權限,其他手機沒有此魔改
     *
     * @param context
     * @return
     */
    private static boolean isHasPermission(Context context, int ops) {
        if (!isMIUI()){
            return true;
        }
        try {
            AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            Method checkOpNoThrow = appOpsManager.getClass().getDeclaredMethod("checkOpNoThrow",
                    int.class, int.class, String.class);
            checkOpNoThrow.setAccessible(true);
            int result = (int) checkOpNoThrow.invoke(appOpsManager, ops, Process.myUid(),
                    context.getPackageName());
            return AppOpsManager.MODE_IGNORED != result;
        } catch (Exception e) {
            RLog.e(TAG, "hasMIUIPermission: " + e.toString());
            e.printStackTrace();
            return false;
        }
    }
 
 
    /**
     * 判斷是否是MIUI
     */
    private static boolean isMIUI() {
        String manufacturer = Build.MANUFACTURER;
        if ("xiaomi".equalsIgnoreCase(manufacturer)) {
            return true;
        }
        return false;
 
    }
 
    /**
     * NFC使用,需要使用權限
     * @return
     */
    public static boolean hasMIUINfcPermission(){
        return MiuiUtils.isHasPermission(context, OPS_NFC);
    }
    /**
     * WiFi使用,需要使用權限
     * @return
     */
    public static boolean hasMIUIWifiPermission(){
        return MiuiUtils.isHasPermission(context, OPS_WIFI);
    }
    /**
     * 去掉 MIUI 權限詳情頁
     * @return
     */
    public static void gotoMIUIPermissionAct(){
        MiuiUtils.jumpToPermissionsEditorActivity(context);
    }
}