背景: 小米手機使用NFC時,會提示是否使用NFC彈窗,如果點選拒絕,則下次碰 NFC 無反應。
一. 現象
NFC 的使用非常簡單,隻需要在 AndroidManifest.xml 上 注冊權限即可:
<uses-permission android:name="android.permission.NFC" />
但 MIUI 可能考慮安全問題,NFC 有使用權限限制,如:

經排查,安卓官網并沒有對 NFC 使用有特殊權限申請,其他手機使用 NFC 無該情況。
二. 問題排查
既然涉及權限問題,可能 MIUI 需要申請權限,抱着疑問使用了:
boolean isHasNfcPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.NFC)
== PackageManager.PERMISSION_GRANTED;
無論關閉還是打開NFC,結果都是 PERMISSION_GRANTED ,看來這裡行不通。
權限問題,除了 ActivityCompat.checkSelfPermission() , 還有 AppOpsManager 權限管理類;
進到源碼,檢視 op 值 ,從數組來看,并發現并沒有跟 NFC 相關的權限。
這個時候就有點蛋疼了。。。
三. 解決問題
後來網上發現了一遍文章,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 的開關
發現有意外收獲。
得到的測試結果如下圖,可以看到 10016 是有變動: 0 → 1
通過反複驗證,得到 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);
}
}