对于应用的开发,往往会通过如下方式判断是否有开启定位权限
int hasCallPhonePermission = MartinApplication.getInstance().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION);
int hasCallPhonePermission1 = MartinApplication.getInstance().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
if (hasCallPhonePermission != PackageManager.PERMISSION_GRANTED) {
return false;
}
其中ACCESS_COARSE_LOCATION表示大致定位,即通过网络定位,ACCESS_FINE_LOCATION表示精确定位,是通过GPS和网络来定位的。
这里有个问题,关于android定位等运行时权限问题,android是从版本5.1才开始支持的,即SDK版本,也就是 Build.VERSION_CODES.LOLLIPOP_MR1
而对于低版本的,则不支持运行时权限。
对于开发的应用设置为minSdkVersion版本低于22,那么在系统应用管理界面设置关闭定位权限的时候,系统会提示“该应用是针对低版本设计的,关闭权限可能导致问题”,如果选择关闭,那么其实系统并不会真的关闭该权限(代码位置packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/model/AppPermissionGroup.java : revokeRuntimePermissions()),而是会检查该项权限是否有AppOp权限,如果有,则会关闭AppOp权限(测试android7.0,关闭定位的AppOp其实并未起作用),然后也会给相应的权限添加flag : FLAG_PERMISSION_REVOKE_ON_UPGRADE,然后在应用升级到合适的版本之后再把相应的权限职位deny。
综上可知,如果想通过开始的那种方法获取应用权限开启状态的话,在低版本的应用中是无法成功的,结果都是granted=true。
附:
查看应用当前权限状态:
dumpsys package <packageName> //如下,因为是低版本应用,所以runtime permissions为空,定位权限虽然关闭,但是granted=true,只是添加了flags=REVOKE_ON_UPGRADE
install permissions:
android.permission.WRITE_SETTINGS: granted=true
android.permission.ACCESS_FINE_LOCATION: granted=true, flags=[ REVOKE_ON_UPGRADE ]
android.permission.SYSTEM_ALERT_WINDOW: granted=true
android.permission.CHANGE_NETWORK_STATE: granted=true
android.permission.RECEIVE_BOOT_COMPLETED: granted=true
android.permission.RECEIVE_SMS: granted=true
android.permission.BLUETOOTH: granted=true
android.permission.GET_TASKS: granted=true
android.permission.INTERNET: granted=true
android.permission.READ_EXTERNAL_STORAGE: granted=true
android.permission.ACCESS_COARSE_LOCATION: granted=true, flags=[ REVOKE_ON_UPGRADE ]
android.permission.READ_PHONE_STATE: granted=true
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS: granted=true
android.permission.SEND_SMS: granted=true
android.permission.CALL_PHONE: granted=true
com.xxxx.zzz.permission.MESSAGE: granted=true
android.permission.FLASHLIGHT: granted=true
android.permission.ACCESS_NETWORK_STATE: granted=true
android.permission.CAMERA: granted=true
com.xxxx.zzz.permission.MIPUSH_RECEIVE: granted=true
android.permission.WRITE_EXTERNAL_STORAGE: granted=true
android.permission.VIBRATE: granted=true
android.permission.ACCESS_WIFI_STATE: granted=true
com.xxxx.zzz.permission.C2D_MESSAGE: granted=true
User 0: ceDataInode=21307 installed=true hidden=false suspended=false stopped=false notLaunched=false enabled=0
gids=[3002, 3003]
runtime permissions:
获取AppOp方法:
appops get <pkgName> //虽然定位已经关闭,但是结果依然是allow,原因未知
COARSE_LOCATION: allow; time=+7s911ms ago
FINE_LOCATION: allow; time=+7s909ms ago
WRITE_SMS: ignore; rejectTime=+56s367ms ago
WRITE_SETTINGS: default; rejectTime=+56s901ms ago
OP_READ_PHONE_STATE: allow; time=+15s939ms ago
READ_EXTERNAL_STORAGE: allow; time=+15s930ms ago
WRITE_EXTERNAL_STORAGE: allow; time=+15s930ms ago
RUN_IN_BACKGROUND: allow; time=+6s104ms ago
而使用另外一个方法则能看到appops的正常状态(经测试android原生设备可以,测试Oppo、meizu手机并无此效果,目测他们实现了自己的针对低版本应用的权限管理机制)
dumpsys appops
Uid u0a125:
COARSE_LOCATION: mode=1 //表示当前appops权限为reject
CALL_PHONE: mode=1
OP_READ_PHONE_STATE: mode=1
Package com.xxxx.zzzz:
COARSE_LOCATION: mode=0; time=+13h28m1s90ms ago; rejectTime=+3m27s329ms ago //上次拒绝时间?
FINE_LOCATION: mode=0; time=+13h28m1s86ms ago; rejectTime=+3m27s329ms ago
WIFI_SCAN: mode=0; rejectTime=+12h56m51s504ms ago
POST_NOTIFICATION: mode=0; time=+12h56m51s323ms ago
CALL_PHONE: mode=0; rejectTime=+13h25m59s363ms ago
WRITE_SMS: mode=1; rejectTime=+13h12m35s578ms ago
故而,可以通过如下反射方式获取该状态下的权限状态(注:获取appops的方法是隐藏方法,所以使用该方法有一定风险)
public static final int OP_COARSE_LOCATION = 0; /** @hide Access to fine location information. */ public static final int OP_FINE_LOCATION = 1; /** * 是否禁用某项操作 */ @TargetApi(Build.VERSION_CODES.KITKAT) public static boolean isAllowed(Context context, int op) {Log.d(TAG, "api level: " + Build.VERSION.SDK_INT); if (Build.VERSION.SDK_INT < 19) {return true; }Log.d(TAG, "op is " + op); String packageName = context.getApplicationContext().getPackageName(); AppOpsManager aom = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); Class<?>[] types = new Class[]{int.class, int.class, String.class}; Object[] args = new Object[]{op, Binder.getCallingUid(), packageName}; try {Method method = aom.getClass().getDeclaredMethod("checkOpNoThrow", types); Object mode = method.invoke(aom, args); Log.d(TAG, "invoke checkOpNoThrow: " + mode); if ((mode instanceof Integer) && ((Integer) mode == AppOpsManager.MODE_ALLOWED)) {Log.d(TAG, "allowed"); return true; }} catch (Exception e) {Log.e(TAG, "invoke error: " + e); e.printStackTrace(); }return false; }