实现思路
这里的实现思路是在系统中新增一个系统应用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: 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接口通信,提供整体思路,不提供源码,避免泄漏安全问题.
最后
如何学习Framework?
由于许多Android开发者日常工作主要集中在业务层面,大量时间用于编写基础代码、应用现成框架,导致对底层技术如Framework、Handler源码、Binder机制等了解不足,仅停留在表面认知阶段。
为此,为了帮助广大开发者弥补这一短板,特此准备了一份详尽的Android Framework内核源码知识体系图解,以及配套的《Android Framework源码开发解析》学习笔记,旨在引导大家系统性地攻克Android Framework领域的核心技术,从而提升自身的竞争力,从容应对金三银四的求职挑战。
【有需要的朋友,扫描下方二维码即可领取!!】👇👇
![](https://img-blog.csdnimg.cn/img_convert/4038c6e257781b5d2ae08c3f72f73223.jpeg)
《Android Framework源码开发揭秘》
第一章 系统启动流程分析
- 第一节 Android启动概括
- 第二节 init.rc解析
- 第三节 Zygote
- 第四节 面试题
第二章 跨进程通信IPC解析
- 第一节 Service还可以这么理解
- 第二节 Binder基础
- 第三节 Binder应用
- 第四节 AIDL应用(上)
- 第五节 AIDL应用(下)
- 第六节 Messenger原理及应用
- 第七节 服务端回调
- 第八节 获取服务(IBinder)
- 第九节 Binder面试题全解析
第三章 Handler源码解析
- 第一节 源码分析
- 第二节 难点问题
- 第三节 Handler常问面试题
第四章 AMS源码解析
- 第一节 引言
- 第二节 Android架构
- 第三节 通信方式
- 第四节 系统启动系列
- 第五节 AMS
- 第六节 AMS面试题解析
第五章 WMS源码解析
- 第一节 WMS与activity启动流程
- 第二节 WMS绘制原理
- 第三节 WMS角色与实例化过程
- 第四节 WMS工作原理
第六章 Surface源码解析
- 第一节 创建流程及软硬件绘制
- 第二节 双缓冲及Surface View解析
- 第三节 Android图形系统综述
第七章 基于Android12.0的SurfaceFlinger源码解析
- 第一节 应用建立和SurfaceFlinger的沟通桥梁
- 第二节 SurfaceFlinger的启动和消息队列处理机制
- 第三节 SurfaceFlinger之VSyns(上)
- 第四节 SurfaceFlinger之VSyns(中)
- 第五节 SurfaceFlinger之VSyns(下)
第八章 PKMS源码解析
- 第一节 PKMS调用方式
- 第二节 PKMS启动过程分析
- 第三节 APK的扫描
- 第四节 APK的安装
- 第五节 PKMS之权限扫描
- 第六节 静默安装
- 第七节 requestPermissions源码流程解析
- 第八节 PKMS面试题
第九章 InputManagerService源码解析
- 第一节 Android Input输入事件处理流程(1)
- 第二节 Android Input输入事件处理流程(2)
- 第三节 Android Input输入事件处理流程(3)
第十章 DisplayManagerService源码解析
- 第一节 DisplayManagerService启动
- 第二节 DisplayAdepter和DisplayDevice的创建
- 第三节 DMS部分亮灭屏流程
- 第四节 亮度调节
- 第五节 Proximity Sensor灭屏原理
- 第六节 Logical Display和Physical Display配置的更新
【有需要的朋友,扫描下方二维码即可领取!!】👇👇
![](https://img-blog.csdnimg.cn/img_convert/4038c6e257781b5d2ae08c3f72f73223.jpeg)
最后
在Android开发市场饱和的大背景下,初级开发者只有通过对Android Framework进行全面而深入的学习与实践,才能有效提高自身的专业素养和技术能力,从而在职场竞争中找准定位,实现从量到质的飞跃,成为行业内的佼佼者。