Android Framework权限篇一之RuntimePermission全体流程
Android Framework权限篇二之RuntimePermission数据结构解析
Android Framework权限篇三之后台定位权限源码剖析
Android Framework权限篇四之AppOps机制
Android Framework权限篇五之完成灵敏权限行为提醒
概述
继之前第三篇文章的后台定位权限源码剖析中提到的灵敏权限行为提醒,这片文章讨论下之前笔者在坚果手机上是怎么完成这样的效果的。
完成思路
这儿的完成思路是在体系中新增一个体系运用app:安全中心;在这儿去监听各个体系服务的回调,依据规矩核算出当时应该展现哪个运用的哪个灵敏权限,告诉给SystemUI进行展现,点击的时分将对应运用移到前台
录音状况查询
录音运用原生供给接口,能够监听录音状况;List<AudioRecordingConfiguration>
能够拿到当时正在运用录音的运用列表,这个是被迫监听方式;
private AudioManager.AudioRecordingCallback mRecordingCallback = new AudioManager.AudioRecordingCallback() {
@Override
public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
super.onRecordingConfigChanged(configs);
...
}
};
mAudioManager.registerAudioRecordingCallback(mRecordingCallback, null);
因为需求考虑安全中心运用挂掉之后重启恢复数据,所以还需求经过接口自动查询;
List<AudioRecordingConfiguration> configs = mAudioManager.getActiveRecordingConfigurations();
相机状况查询
- 相机没有像录音的原生接口,需求相机Framework服务侧供给支持
- 完成:相机服务会将运用相机的运用耐久化到SystemProperties,安全中心再依据camera 回调onCameraAvailable(close camera)/onCameraUnavailable(open camera) 在这两个回调时机时去查询SystemProperties得到正在运用相机的运用列表
- 这两个办法会在初次注册的时分就收到回调,能够当作安全中心运用挂掉重启后进行自动查询
private CameraManager.AvailabilityCallback mListener = new CameraManager.AvailabilityCallback() {
@Override
public void onCameraAvailable(String cameraId) {
...
}
@Override
public void onCameraUnavailable(String cameraId) {
...
}
};
mCameraManager.registerAvailabilityCallback(mListener, null);
定位状况查询
- 定位服务没有原生接口,Framework侧会在开端/完毕定位时发送播送,播送中携带正在运用定位的运用列表
- 定位播送是粘性播送,在安全中心挂掉重启今后能够再响应最近的一次播送拿到正在运用定位的运用列表进行更新
String LOCATION_APPS_ACTION = "smartisan.intent.action.LOCATION_LISTENER_LIST";
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (LOCATION_APPS_ACTION.equals(action)) {
...
}
}
};
保护前台列表
- 因为需求判别运用是否在后台运用相机/录音/定位服务,所以需求保护一个前台运用列表,假如正在运用服务的运用不在前台列表中,则认为是在后台运用服务,需求告诉SystemUI展现bar
- 这儿经过ActivityManager监听resume则将运用加入前台列表,pause则移除前台列表;
private IActivityLifeCycleObserver mLifeCycleObserver = new IActivityLifeCycleObserver.Stub() {
@Override
public void onActivityResumeForeground(String pkg, String activity, int uid) {
...
}
@Override
public void onActivityPauseBackground(String pkg, String activity, int uid) {
...
}
}
ActivityManager.getService().registerActivityLifeCycleObserver(mLifeCycleObserver);
相同当安全中心挂掉重启时需求自动查询接口:
public LinkedHashSet<String> getRunningForegroundPkgList(boolean ignoreSystem) {
LinkedHashSet<String> forePkgList = new LinkedHashSet<>();
final List<ActivityManager.RunningAppProcessInfo> processInfos =
mAm.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo processInfo : processInfos) {
if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
&& processInfo.processState == ActivityManager.START_TASK_TO_FRONT) {
for (String pkg : processInfo.pkgList) {
...
forePkgList.add(pkg);
}
}
}
return forePkgList;
}
进程完毕状况
- 运用进程退出时(可能是用户手动清理后台或者溃散退出)相机/录音/定位等服务的接口会回调状况更新,然后更新对应正在运用服务的运用列表
- 前台列表的
ActivityManager pasue resume
接口不会收到回调,所以需求额外处理下这种场景case,经过以下接口进行监听,经过onProcessDied拿到的uid能够查询到运用包名,再将其从前台列表移除
private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
}
@Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
}
@Override
public void onProcessDied(int pid, int uid) {
...
}
};
ActivityManager.getService().registerProcessObserver(mProcessObserver);
展现规矩核算
- 前面经过各种服务回调拿到相关数据列表,进行判别今后更新到map中
- 当有多个运用运用多个权限时,显现的时分依照如下规矩展现
- 优先依照权限优先级:相机>录音>定位
- 其次依照最近运用的时刻对运用进行排序
这儿运用LinkedHashMap:键是包名,值是对应的权限列表的数据结构进行存储
//这儿第三个参数设置为true能够确保map新增/更新的item会放在末尾处,以确保次序
private LinkedHashMap<String, List<Integer>> mPkgAndPermData = new
LinkedHashMap<>(INIT_CAPACITY, LOAD_FACTOR, true);
权限 | 标识 | 优先级(数值越大越高) |
---|---|---|
定位 | location | 1 |
录音 | audio | 2 |
相机 | camera | 3 |
假如map当中存储数据如下:
String:pkg | List<Integer>: permissionList |
---|---|
com.tencent.mm | 1,2 |
com.tencent.qq | 2,3 |
则依据如下规矩核算,因为map是依照最近运用的运用排序的,即最近更新的运用会排在末尾,所以遍历完之后,会得到权限级别最高和对应的最近运用:如上得到的结果是:QQ-相机权限 告诉给SystemUI
String packageName = null;
int permissionLevel = PermissionDataManager.PERMISSION_NULL;
for (Map.Entry<String, List<Integer>> entry : data.entrySet()) {
List<Integer> valueList = new ArrayList<>(entry.getValue());
int level = getHighestLevel(valueList, !isGpsAllowed);
if (level >= permissionLevel) {
packageName = entry.getKey();
permissionLevel = level;
}
}
SystemUI通讯
最终bar的展现是在SystemUI,安全中心与SystemUI进行通讯,将需求展现的权限和点击跳转的包名传给SystemUI
通讯方式
SystemUI运用经过bindService的形式,绑定安全中心供给的service进行通讯;界说aidl接口;
public class SensitivePermissionService extends Service {
@Override
public void onCreate() {
super.onCreate();
...
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private Binder mBinder = new ISensitivePermission.Stub() {
@Override
public void registerLatestPermissionObserver(IPermissionObserver observer, boolean sticky)
throws RemoteException {
...
}
@Override
public void unregisterLatestPermissionObserver(IPermissionObserver observer)
throws RemoteException {
...
}
};
}
aidl接口IPermissionObserver如下,对应的参数描绘如下:
interface IPermissionObserver {
void onPermissionChanged(String packageName, in String[] permissions, int userId);
}
import com.smartisanos.securitycenter.IPermissionObserver;
interface ISensitivePermission {
void registerLatestPermissionObserver(IPermissionObserver observer, boolean sticky);
void unregisterLatestPermissionObserver(IPermissionObserver observer);
}
参数称号 | 类型 | 描绘 | 空情况 |
---|---|---|---|
packageName | String | 运用包名 | “” |
permissions | String[] | 权限名:默认情况下只要一个权限 audio/camera/location | new String[] { } |
userId | int | 区分双开运用,默认是0,双开则为10 | 0 |
SystemUI侧经过bindService方式绑定服务,注册监听,当服务端有改变时会告诉客户端;
void bindService() {
Intent intent = new Intent(ACTION);
intent.setPackage(PACKAGE);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
这儿详细的运用参阅aidl接口通讯,供给全体思路,不供给源码,避免走漏安全问题;
结语
坚果手机上的全体思路方案便是如此,其他手机厂商感兴趣的能够借鉴看下。