AppOpsMananger
之前在开发一个统计应用使用情况功能,自然用到了UsageState。想要从UsageState中读到数据,需要我们手动添加权限。
Log.i("UsageStateUtil", "跳转到软件使用情况权限设置");
Intent intent = new Intent("android.settings.USAGE_ACCESS_SETTINGS");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.context.startActivity(intent);
throw new Exception("读取应用使用情况,需要权限");
但是我们这悄无声息的统计应用使用,就不好弹出来要求用户去授予权限了。本着这个想法,我觉得翻翻Settings应用,找找这个权限界面到底是怎么授予权限的。
行动
很快找到关于UsageState的代码:
最后定位在UsageAccessSettings.java中
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
……
// Check if we need to do any work.
if (pe.appOpMode != newMode) {
if (newMode != AppOpsManager.MODE_ALLOWED) {
// Turning off the setting has no warning.
setNewMode(pe, newMode);
return true;
} ……
……
最后调用了setMode
void setNewMode(PackageEntry pe, int newMode) {
mAppOpsManager.setMode(AppOpsManager.OP_GET_USAGE_STATS,
pe.packageInfo.applicationInfo.uid, pe.packageName, newMode);
pe.appOpMode = newMode;
}
不用多想,这种授予权限的API自然不会随便就能用的。系统权限!我的APP 就可以有系统签名啊,哈哈哈哈~
惊奇发现
这么多权限!?
/** @hide No operation specified. */
public static final int OP_NONE = -1;
/** @hide Access to coarse location information. */
public static final int OP_COARSE_LOCATION = 0;
/** @hide Access to fine location information. */
public static final int OP_FINE_LOCATION = 1;
/** @hide Causing GPS to run. */
public static final int OP_GPS = 2;
/** @hide */
public static final int OP_VIBRATE = 3;
/** @hide */
public static final int OP_READ_CONTACTS = 4;
/** @hide */
public static final int OP_WRITE_CONTACTS = 5;
/** @hide */
public static final int OP_READ_CALL_LOG = 6;
/** @hide */
public static final int OP_WRITE_CALL_LOG = 7;
/** @hide */
public static final int OP_READ_CALENDAR = 8;
/** @hide */
public static final int OP_WRITE_CALENDAR = 9;
/** @hide */
public static final int OP_WIFI_SCAN = 10;
/** @hide */
public static final int OP_POST_NOTIFICATION = 11;
/** @hide */
public static final int OP_NEIGHBORING_CELLS = 12;
/** @hide */
public static final int OP_CALL_PHONE = 13;
/** @hide */
public static final int OP_READ_SMS = 14;
/** @hide */
public static final int OP_WRITE_SMS = 15;
/** @hide */
public static final int OP_RECEIVE_SMS = 16;
/** @hide */
public static final int OP_RECEIVE_EMERGECY_SMS = 17;
/** @hide */
public static final int OP_RECEIVE_MMS = 18;
/** @hide */
public static final int OP_RECEIVE_WAP_PUSH = 19;
/** @hide */
public static final int OP_SEND_SMS = 20;
/** @hide */
public static final int OP_READ_ICC_SMS = 21;
/** @hide */
public static final int OP_WRITE_ICC_SMS = 22;
/** @hide */
public static final int OP_WRITE_SETTINGS = 23;
/** @hide */
public static final int OP_SYSTEM_ALERT_WINDOW = 24;
/** @hide */
public static final int OP_ACCESS_NOTIFICATIONS = 25;
/** @hide */
public static final int OP_CAMERA = 26;
/** @hide */
public static final int OP_RECORD_AUDIO = 27;
/** @hide */
public static final int OP_PLAY_AUDIO = 28;
/** @hide */
public static final int OP_READ_CLIPBOARD = 29;
/** @hide */
public static final int OP_WRITE_CLIPBOARD = 30;
/** @hide */
public static final int OP_TAKE_MEDIA_BUTTONS = 31;
/** @hide */
public static final int OP_TAKE_AUDIO_FOCUS = 32;
/** @hide */
public static final int OP_AUDIO_MASTER_VOLUME = 33;
/** @hide */
public static final int OP_AUDIO_VOICE_VOLUME = 34;
/** @hide */
public static final int OP_AUDIO_RING_VOLUME = 35;
/** @hide */
public static final int OP_AUDIO_MEDIA_VOLUME = 36;
/** @hide */
public static final int OP_AUDIO_ALARM_VOLUME = 37;
/** @hide */
public static final int OP_AUDIO_NOTIFICATION_VOLUME = 38;
/** @hide */
public static final int OP_AUDIO_BLUETOOTH_VOLUME = 39;
/** @hide */
public static final int OP_WAKE_LOCK = 40;
/** @hide Continually monitoring location data. */
public static final int OP_MONITOR_LOCATION = 41;
/** @hide Continually monitoring location data with a relatively high power request. */
public static final int OP_MONITOR_HIGH_POWER_LOCATION = 42;
/** @hide Retrieve current usage stats via {@link UsageStatsManager}. */
public static final int OP_GET_USAGE_STATS = 43;
/** @hide */
public static final int OP_MUTE_MICROPHONE = 44;
/** @hide */
public static final int OP_TOAST_WINDOW = 45;
/** @hide Capture the device's display contents and/or audio */
public static final int OP_PROJECT_MEDIA = 46;
/** @hide Activate a VPN connection without user intervention. */
public static final int OP_ACTIVATE_VPN = 47;
/** @hide */
public static final int _NUM_OP = 48;
不看不知道,一看吓一跳。这么多权限可以通过setMode来设置,这可把我高兴坏了。
二话不说,先试试我需要的权限:
/** @hide Retrieve current usage stats via {@link UsageStatsManager}. */
public static final int OP_GET_USAGE_STATS = 43;
public static final int OP_GET_USAGE_STATS = 43;
String pkg = intent.getStringExtra("packageName");
int newMode = intent.getIntExtra("mode", AppOpsManager.MODE_ALLOWED);
int code = intent.getIntExtra("code", -1);
AppOpsManager aom;
try {
aom = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Class<AppOpsManager> appOpsManagerClass;
appOpsManagerClass = AppOpsManager.class;
// int code, int uid, String packageName, int mode
Method method = appOpsManagerClass.getMethod("setMode", int.class, int.class, String.class, int.class);
method.setAccessible(true);
try {
int uid = 0;
PackageManager manager = context.getPackageManager();
ApplicationInfo ai = manager.getApplicationInfo(pkg, PackageManager.GET_META_DATA);
uid = ai.uid;
method.invoke(aom, code, uid, pkg, newMode);
} catch (Exception e) {
}
} catch (Exception e) {
}
亲测可行!
冷水
查看用户应用使用情况权限的获取就这样拿到了,是不是其他的权限也可以?满怀希望地开始测试,结果却不是那样的。
整理的结果如下:
验证AppOpsManager权限开启情况
设备:HUAWEI Mate7 -TL00;
Android 版本:6.0;
GPS 的权限:0,1,2,41,42-------------------》(该权限在设置应用中打开,手机管家也存在,页面一致)执行完页面无变化。(有禁用、提示、启用选项)
读写联系人权限:4,5(该权限在设置应用中打开,手机管家也存在,页面一致)执行完页面无变化。(有禁用、提示、启用选项)
设备:HUAWEI Mate7 -TL00;
Android 版本:6.0;
GPS 的权限:0,1,2,41,42-------------------》(该权限在设置应用中打开,手机管家也存在,页面一致)执行完页面无变化。(有禁用、提示、启用选项)
读写联系人权限:4,5(该权限在设置应用中打开,手机管家也存在,页面一致)执行完页面无变化。(有禁用、提示、启用选项)
……(所有权限选择中有禁用、提示、启用选项的,无法通过API来开启,受限于手机管家)
通知栏显示权限:11,25,38)11就可以开启通知栏权限,25,38无具体作用(该权限在设置应用中打开,手机管家也存在,页面一致)(没有有禁用、提示、启用选项,只 有一个开关)
悬浮窗权限:24; 可以开启(该权限在设置应用中打开,手机管家也存在,页面一致)(无有禁用、提示、启用选项,只有一个开关)
读取应用使用情况权限:43; 可以开启(该权限在设置应用中打开,手机管家也存在,页面一致)(无有禁用、提示、启用选项,只有一个开关)
自动启动权限:没有
受保护应用:没有
通知栏显示权限:11,25,38)11就可以开启通知栏权限,25,38无具体作用(该权限在设置应用中打开,手机管家也存在,页面一致)(没有有禁用、提示、启用选项,只 有一个开关)
悬浮窗权限:24; 可以开启(该权限在设置应用中打开,手机管家也存在,页面一致)(无有禁用、提示、启用选项,只有一个开关)
读取应用使用情况权限:43; 可以开启(该权限在设置应用中打开,手机管家也存在,页面一致)(无有禁用、提示、启用选项,只有一个开关)
自动启动权限:没有
受保护应用:没有
------------------------------------------------------我是分隔符------------------------------------------------------------------------------
哪些个权限无法通过setMode 来管控,我的猜想是各个厂商对比较隐私的权限做了特殊处理,只有手机管家这样的预置软件才能进行管控。
但是如果权限管理的数据保存在一个地方,我们可以尝试修改这个文件即可获取到权限不是么?秉着这个想法,源码继续看下去:
/** @hide */
public void setMode(int code, int uid, String packageName, int mode) {
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
}
}
这个mSerivice 就是 final IAppOpsService mService; 一个通过Aidl通信的接口。
interface IAppOpsService {
// These first methods are also called by native code, so must
// be kept in sync with frameworks/native/include/binder/IAppOpsService.h
int checkOperation(int code, int uid, String packageName);
int noteOperation(int code, int uid, String packageName);
int startOperation(IBinder token, int code, int uid, String packageName);
void finishOperation(IBinder token, int code, int uid, String packageName);
void startWatchingMode(int op, String packageName, IAppOpsCallback callback);
void stopWatchingMode(IAppOpsCallback callback);
IBinder getToken(IBinder clientToken);
// Remaining methods are only used in Java.
int checkPackage(int uid, String packageName);
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
void setMode(int code, int uid, String packageName, int mode);
void resetAllModes();
int checkAudioOperation(int code, int usage, int uid, String packageName);
void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages);
void setUserRestrictions(in Bundle restrictions, int userHandle);
void removeUser(int userHandle);
}
/ These first methods are also called by native code, so must
// be kept in sync with frameworks/native/include/binder/IAppOpsService.h
int checkOperation(int code, int uid, String packageName);
int noteOperation(int code, int uid, String packageName);
int startOperation(IBinder token, int code, int uid, String packageName);
void finishOperation(IBinder token, int code, int uid, String packageName);
void startWatchingMode(int op, String packageName, IAppOpsCallback callback);
void stopWatchingMode(IAppOpsCallback callback);
IBinder getToken(IBinder clientToken);
// Remaining methods are only used in Java.
int checkPackage(int uid, String packageName);
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
void setMode(int code, int uid, String packageName, int mode);
void resetAllModes();
int checkAudioOperation(int code, int usage, int uid, String packageName);
void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages);
void setUserRestrictions(in Bundle restrictions, int userHandle);
void removeUser(int userHandle);
}
实现接口就是AppOpsService 的setMode
仔细看下来,主要是两个动作:readState() 和writeState(),顾名思义就是对于权限进行读取然后重新更新写入。
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
修改的文件就是:new File(systemDir, "appops.xml")
将该文件打印出来,发现结果蛮清晰,但是有些权限的授予或者取消并不是都体现在此;
<Ops>
<package>
<package>
……
尝试修改该文件,但是并没有获取到对应的权限(这些就不做展示了,有兴趣自己来改写看看)
修改过的文件,过一段时间就会恢复成原来的内容;
结论
这些我的猜想是:1.厂商修改过这个服务 ;2.权限管理文件appops.xml 可能有备份,并被其他服务监听;3.修改权限时会获取手机管家的权限部分的数据库,隐私权限的管理,系统会根据这部分数据库来管理。
这只是猜想,如果有哪些不完整或者错误的说法,希望大家指出。
对于权限管理的方案,如果大家有比较清晰、正确的思路,希望可以分享。