Android 手机应用分身
1、实现手机应用分身的方式
当前市面上实现手机分身的方式主要有三类:
-
修改Framework -> 使用
Android多用户机制
进行实现该方式适用于手机厂商,修改底层代码,通过创建多用户的方法来实现手机分身功能。
通过getFileDir()的api发现,在本体得到的是
data/data/com.xxx.xxx
,克隆得到的是data/user/10/com.xxx.xxx
-
修改apk
通过反编译apk,修改apk的包名、签名等将apk伪装成另一个app,市面上常见的第三方多开app大部分都是使用该技术。其特点是每次制作一个分身都需要时间进行一个拷贝、并且在应用列表中可以看到
参考资料:
https://blog.csdn.net/weixin_43970718/article/details/119116441
-
虚拟操作系统
虚拟一个操作系统,克隆体app在这个虚拟系统中运行,在应用列表不可见,代表产品:360分身大师
检测方案:
这个据说是唯一一种绕过getFileDir()的分身方式,确实这种方式让我耗费了很长时间,下面以360的分身大师举例,详细说下分析过程- 首先通过getFileDir()尝试,本体和克隆体输出一致。失败!!
- 通过在本体和克隆体登录不同账号获取SP中存储的账号信息,确定获取的信息不同,证明存储位置不一致
- 通过文件管理器查看最近修改的文件,发现系统根目录下有一个docker的文件夹,里面包含了跟根目录类似的结构,在其里面的Android/data下就会发现我们自己的包名,分身的本地数据就存放在这里。
- 找到分身的本地存储后,本想向其中存储数据用来标记是否为分身,但本体运行时报了一个无权限写入的异常,因此改为了判断当前是否有对该目录的写入权限
2、Android 多用户
2.1、多用户简介
从Android 4.0开始,Google就开始在Android上布局多用户,UserManager
因此而诞生。
2.2、基础概念
多用户的一些基础概念:
- Uid(用户id):在Linux上,一个用户Uid标识着一个给定的用户。Android上也沿用了Linux用户概念,Root用户Uid为0,System Uid为1000,并且,每个应用程序在安装时也被赋予了单独的Uid,这个Uid将伴随着应用从安装到卸载。
- Gid(用户组id):Linux上规定每个应用都应该有一个用户组,对于Android应用程序来说,每个应用的所属用户组与Uid相同。
- Gids:应用在安装后所获得权限的Id集合。在Android上,每个权限都可能对应一个或多个group,每个group有个gid name,gids就是通过每个gid name计算得出的id集合,一个UID可以关联GIDS,表明该UID拥有多种权限。
2.3、多户用特性
-
独立的
userId
Android在创建每个用户时,都会分配一个整型的userId。对于主用户(正常下的默认用户)来说,userId为0,之后创建的userId将从10开始计算,每增加一个userId加1。
K631:/ # pm list users Users:UserInfo{0:机主:c13} runningUserInfo{10:clone:1010} running
创建一个名为“ulangch”的用户:
root@virgo:/ # pm create-user "ulangch" Success: created user id 11K631:/ # pm list users Users:UserInfo{0:机主:c13} runningUserInfo{10:clone:1010} runningUserInfo{11:ulangch:400} running
启动和切换到该用户:
root@virgo:/ # am start-user 11 Success: user started root@virgo:/ # am switch-user 11
-
独立的文件存储
为了多用户下的数据安全性,在每个新用户创建之初,不管是外部存储(External Storage)还是app data目录,Android都为其准备了独立的文件存储。
多用户下的/storage分区:
K631:/ # ls -l /storage/emulated total 9 drwxrws--- 16 media_rw media_rw 3452 2022-09-08 17:44 0 drwxrws--- 15 media_rw media_rw 3452 2022-09-14 10:10 10 drwxrwx--- 2 media_rw media_rw 3452 1970-01-01 08:03 obbK631:/ # ls -l /sdcard lrw-r--r-- 1 root root 21 2022-09-08 17:44 /sdcard -> /storage/self/primaryK631:/ # ls -l /storage/self/primary lrwxrwxrwx 1 root root 19 2022-09-15 19:19 /storage/self/primary -> /storage/emulated/0
新用户创建时,Android在 “/storage/emulated” 目录下为每个用户都创建了名为用户id的目录,当我们在代码中使用 “Environment.getExternalStorageDirectory().absolutePath” 获取外部存储路径时,返回的就是当前用户下的对应目录(如:userId = 11, 则返回为 “/storage/emulated/10”)。
另外,可以看出,我们平常说到的 “/sdcard” 目录其实最终也是软链到了 “/storage/emulated/0”
多用户下的/data分区
K631:/ # ls -l data/user/ total 60 drwxrwx--x 199 system system 24576 2022-09-15 11:13 0 drwxrwx--x 182 system system 20480 2022-09-16 10:57 10 drwxrwx--x 197 system system 24576 2022-09-16 11:00 11K631:/ # ls -l /data/user/10/com.tencent.mm/ total 6 drwxrws--x 2 u10_a148 u10_a148_cache 3452 2022-09-16 11:21 cache drwxrws--x 2 u10_a148 u10_a148_cache 3452 2022-09-16 11:21 code_cache
与External Storage相同,新用户创建时,Android也会在 “data/user” 目录下创建了名为userId的目录,用于存储该用户中所有App的隐私数据,如果在代码中使用 “Context.getFilesDir()” 来获取应用的data目录,不同User下也会有不同。
另外,也可以看出,平常说到的 “/data/data” 目录其实也是软链到了 “/data/user/0”。
注:在Android中,应用的uid是和当前的用户有关的,同一个应用具有相同的appId,其uid的计算方式为:
uid = userId * 1000000 + appId
,在主用户中,uid = appId。 -
独立的权限控制
-
不同的用户具有权限不同,如:访客用户的默认权限限制就有:
perseus:/ $ dumpsys user ...Guest restrictions:no_sms // 限制发送短信no_install_unknown_sources // 限制安装no_config_wifi // 限制配置WiFino_outgoing_calls // 限制拨打电话
(注:使用 “adb shell dumpsys user” 可以查看所有的用户信息,如userId、name、restrictions等)
这些权限可以在创建用户时规定,也可以后期由系统动态设置。
-
不同用户下App的应用权限是独立的
前面说到,uid与userId存在一种计算关系(uid = userId * 1000000 + appId),而在系统中对于权限控制也是根据uid和对应的userId来判定的,因此不同用户下相同应用可以具有不同的权限。
-
-
App安装的唯一性
App的文件存储和数据目录在不同用户下都是独立的,但是对于App的安装,多个用户下同一个App却保持着同一个安装目录,即:
-
普通三方app:
/data/app/
-
普通系统应用:
/system/app
-
特权系统应用:
/system/priv-app
K631:/ # ls /system/app/MTBFTool/ MTBFTool.apk oat
拓展:权限在声明时安全等级(protectionLevel)分为3类:
<permission android:name="android.permission.READ_EXTERNAL_STORAGE"android:permissionGroup="android.permission-group.STORAGE"android:label="@string/permlab_sdcardRead"android:description="@string/permdesc_sdcardRead"android:protectionLevel="dangerous" /><!-- Allows applications to access information about Wi-Fi networks.<p>Protection level: normal --> <permission android:name="android.permission.ACCESS_WIFI_STATE"android:description="@string/permdesc_accessWifiState"android:label="@string/permlab_accessWifiState"android:protectionLevel="normal" /><!-- @SystemApi Allows applications to set the system time.<p>Not for use by third-party applications. --> <permission android:name="android.permission.SET_TIME"android:protectionLevel="signature|privileged" />
- normal:普通权限,在AndroidManifest.xml中声明就可以获取权限,如 INTERNET 权限
- dangerous:敏感权限,需要动态申请告知用户才能获取
- singature|privileged:具有系统签名的系统应用才可以获取的权限,对应上方的
/system/priv-app
因此,多用户下的应用其实只安装一次,不同用户下同一个应用的版本和签名都应该相同,不同用户下相同App能够独立运行是因为系统为他们创造了不同的运行环境和权限。
-
-
kernel及系统进程的不变性
在不同用户下,虽然能够看到不同的桌面,不同的运行环境,一切都感觉是新的,但是我们系统本身并没有发生改变,kernel进程、system_server进程以及所有daemon进程依然是同一个,并不会重启。
而如果我们在不同用户中开启相同的app,我们可以看到可以有多个app进程,而他们的父进程都是同一个,即 zygote:
参考资料:https://blog.csdn.net/misiyuan/article/details/77432020?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ETopNSimilar%7Edefault-1-77432020-blog-94654401.topnsimilarv1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ETopNSimilar%7Edefault-1-77432020-blog-94654401.topnsimilarv1&utm_relevant_index=2
3、UserManager
多用户模型:
framework/base/services/core/java/com/android/server/pm/UserManagerService.java
framework/base/core/java/android/os/UserManager.java
framework/base/core/java/android/content/pm/UserInfo.java
framework/base/core/java/android/os/UserHandle.java
链接:[多用户管理UserManager - Gityuan博客 | 袁辉辉的技术博客](http://gityuan.com/2016/11/20/user_manager/)
3.1、概述
Android 多用户模型,通过UserManagerService对多用户进行创建、删除、查询等管理操作。
- Binder服务端:UserManagerService继承于IUserManager.Stub,作为 binder 服务端。
- Binder客户端:UserManager的成员变量mService继承于IUserManager.Stub.Proxy,该变量作为binder客户端。UserManager的大部分核心工作都是交由其成员变量mService,再通过binder调用到UserManagerService所相应的方法。
3.1.1、概念
userId,uid,appid,ShareAppGid概念关系
- userId:是指用户Id;
- appId: 是指跟用户空间无关的应用程序id;取值范围 0<= appId <100000
- uid:是指跟用户空间紧密相关的应用程序id;
- SharedAppGid:是指可共享的应用id;
转换关系:
uid = userId * 100000 + appId
SharedAppGid = 40000 + appId
另外,PER_USER_RANGE = 100000, 意味着每个user空间最大可以有100000个appid。这些区间分布如下:
- system appid: [1000, 9999]
- application appid:[10000, 19999]
- Shared AppGid: [50000, 59999]
- isolated appid: [99000, 99999]
3.1.2、UserHandle
常见方法:
方法 | 含义 |
---|---|
isSameUser | 比较两个uid的userId是否相同 |
isSameApp | 比较两个uid的appId是否相同 |
isApp | appId是否属于区间[10000,19999] |
isIsolated | appId是否属于区间[99000,99999] |
getIdentifier | 获取UserHandle所对应的userId |
常见成员变量:(UserHandle的成员变量mHandle便是userId)
userId | 赋值 | 含义 |
---|---|---|
USER_OWNER | 0 | 拥有者 |
USER_ALL | -1 | 所有用户 |
USER_CURRENT | -2 | 当前活动用户 |
USER_CURRENT_OR_SELF | -3 | 当前用户或者调用者所在用户 |
USER_NULL | -1000 | 未定义用户 |
类成员变量:
UserHandle OWNER = new UserHandle(USER_OWNER); // 0
UserHandle ALL = new UserHandle(USER_ALL); // -1
UserHandle CURRENT = new UserHandle(USER_CURRENT); // -2
UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF); // -3
关于UID默认情况下,客户端可使用rocess.myUserHandle(); 服务端可使用UserHandle.getCallingUserId();
3.1.3、UserInfo
UserInfo代表的是一个用户的信息,涉及到的flags及其含义,如下:
flags | 含义 |
---|---|
FLAG_PRIMARY | 主用户,只有一个user具有该标识 |
FLAG_ADMIN | 具有管理特权的用户,例如创建或删除其他用户 |
FLAG_GUEST | 访客用户,可能是临时的 |
FLAG_RESTRICTED | 限制性用户,较普通用户具有更多限制,例如禁止安装app或者管理wifi等 |
FLAG_INITIALIZED | 表明用户已初始化 |
FLAG_MANAGED_PROFILE | 表明该用户是另一个用户的轮廓 |
FLAG_DISABLED | 表明该用户处于不可用状态 |
3.1.4、UserState
//用户启动中
public final static int STATE_BOOTING = 0;
//用户正常运行状态
public final static int STATE_RUNNING = 1;
//用户正在停止中
public final static int STATE_STOPPING = 2;
//用户处于关闭状态
public final static int STATE_SHUTDOWN = 3;
用户生命周期线:
STATE_BOOTING -> STATE_RUNNING -> STATE_STOPPING -> STATE_SHUTDOWN.
3.2、UserManager的使用
//获取UserManager
UserManager mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
//获取所有的用户信息
List<UserInfo> users = UserManager.getUsers(true);
//当前用户的id
int myUserId = UserHandle.myUserId();
//根据id获取当前用户的信息
UserInfo myUserInfo = mUserManager.getUserInfo(myUserId);
//用户icon的路径
String iconPath = myUserInfo.iconPath;
//用户icon的Bitmap
Bitmap b = mUserManager.getUserIcon(myUserId);
//当前用户是访客
boolean mIsGuest = myUserInfo.isGuest();
//当前用户是管理员,只有管理员添加或者移除其他用户
boolean mIsAdmin = myUserInfo.isAdmin();
//当前用户是否是受限制的
boolean isRestricted = myUserInfo.isRestricted();
//UserManager是否支持切换用户
boolean canSwitchUsers = mUserManager.canSwitchUsers();//循环遍历所有的用户
for (UserInfo user : users) {//该用户必须是要支持被切换的if (!user.supportsSwitchToByUser()) {continue;}if (user.id == UserHandle.myUserId()) {//当前用户的信息} else if (user.isGuest()) {//当前用户是否是访客continue;} else {if (user.isAdmin()) {//如果用户是管理,那就显示管理员}}//当前用户是否被初始化,如果没有初始化,可以做一次初始化if (!isInitialized(user)) {} else if (user.isRestricted()) {//用户是受限制的}
}//监听用户的移除,添加和切换
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
context.registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null, mHandler);private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {//用户被移除............} else if (intent.getAction().equals(Intent.ACTION_USER_INFO_CHANGED)) {//用户信息改变int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);............}}
};//创建受限制的用户
UserInfo newUserInfo = mUserManager.createRestrictedProfile(mAddingUserName);//创建用户
UserInfo newUserInfo = mUserManager.createUser(mAddingUserName, 0);//移除当前用户,还需要再指定系统用户;
ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
mUserManager.removeUser(UserHandle.myUserId());//切换用户
ActivityManager.getService().switchUser(userId);
4、逻辑实现
4.1、进入设置中的应用界面逻辑代码
AppDashboardFragment.java
,通过getPreferenceScreenResId()
进入应用界面的布局文件app.xml
/** Copyright (C) 2021 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.settings.applications;import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.SearchIndexableResource;import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/** Settings page for apps. */
@SearchIndexable
public class AppDashboardFragment extends DashboardFragment {private static final String TAG = "AppDashboardFragment";private AppsPreferenceController mAppsPreferenceController;private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {final List<AbstractPreferenceController> controllers = new ArrayList<>();controllers.add(new AppsPreferenceController(context));return controllers;}@Overridepublic int getMetricsCategory() {return SettingsEnums.MANAGE_APPLICATIONS;}@Overrideprotected String getLogTag() {return TAG;}@Overridepublic int getHelpResource() {return R.string.help_url_apps_and_notifications;}@Overrideprotected int getPreferenceScreenResId() {return R.xml.apps;}@Overridepublic void onAttach(Context context) {super.onAttach(context);use(SpecialAppAccessPreferenceController.class).setSession(getSettingsLifecycle());mAppsPreferenceController = use(AppsPreferenceController.class);mAppsPreferenceController.setFragment(this /* fragment */);getSettingsLifecycle().addObserver(mAppsPreferenceController);final HibernatedAppsPreferenceController hibernatedAppsPreferenceController =use(HibernatedAppsPreferenceController.class);getSettingsLifecycle().addObserver(hibernatedAppsPreferenceController);}@Overrideprotected List<AbstractPreferenceController> createPreferenceControllers(Context context) {return buildPreferenceControllers(context);}public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =new BaseSearchIndexProvider() {@Overridepublic List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) {final SearchIndexableResource sir = new SearchIndexableResource(context);sir.xmlResId = R.xml.apps;return Arrays.asList(sir);}@Overridepublic List<AbstractPreferenceController> createPreferenceControllers(Context context) {return buildPreferenceControllers(context);}};
}
<?xml version="1.0" encoding="utf-8"?>
<!--Copyright (C) 2020 The Android Open Source ProjectLicensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License athttp://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.--><PreferenceScreenxmlns:android="http://schemas.android.com/apk/res/android"xmlns:settings="http://schemas.android.com/apk/res-auto"android:key="apps_screen"android:title="@string/apps_dashboard_title"><Preferenceandroid:key="all_app_infos"android:title="@string/all_apps"android:summary="@string/summary_placeholder"android:order="-999"android:fragment="com.android.settings.applications.manageapplications.ManageApplications"settings:keywords="@string/keywords_applications_settings"/><!-- 最近打开的应用 --><PreferenceCategoryandroid:key="recent_apps_category"android:title="@string/recent_app_category_title"android:order="-998"settings:searchable="false"><!-- Placeholder for a list of recent apps --><!-- See all apps --><Preferenceandroid:key="see_all_apps"android:title="@string/default_see_all_apps_title"android:icon="@drawable/ic_chevron_right_24dp"android:fragment="com.android.settings.applications.manageapplications.ManageApplications"android:order="5"settings:searchable="false"></Preference></PreferenceCategory><PreferenceCategoryandroid:key="general_category"android:title="@string/category_name_general"android:order="-997"android:visibility="gone"settings:searchable="false"/><Preferenceandroid:key="default_apps"android:title="@string/app_default_dashboard_title"android:order="-996"settings:controller="com.android.settings.applications.DefaultAppsPreferenceController"><intent android:action="android.settings.MANAGE_DEFAULT_APPS_SETTINGS"/></Preference><Preferenceandroid:key="game_settings"android:title="@string/game_settings_title"android:summary="@string/game_settings_summary"android:order="-995"settings:controller="com.android.settings.applications.GameSettingsPreferenceController"></Preference><PreferenceCategoryandroid:key="dashboard_tile_placeholder"android:order="10"/><Preferenceandroid:key="hibernated_apps"android:title="@string/unused_apps"android:summary="@string/summary_placeholder"android:order="15"settings:keywords="app_hibernation_key"settings:controller="com.android.settings.applications.HibernatedAppsPreferenceController"><intent android:action="android.intent.action.MANAGE_UNUSED_APPS"/></Preference><Preferenceandroid:key="special_access"android:fragment="com.android.settings.applications.specialaccess.SpecialAccessSettings"android:title="@string/special_access"android:order="20"settings:controller="com.android.settings.applications.SpecialAppAccessPreferenceController"/><!-- 应用分身 --><Preferenceandroid:key="parallel_app"android:fragment="com.android.settings.applications.parallelapp.ParallelAppSettings"android:title="@string/parallel_app"android:order="25"settings:controller="com.android.settings.applications.parallelapp.ParallelAppPreferenceController"/>
</PreferenceScreen>
4.2、进入手机分身的fragment类
ParallelAppSettings.java
/** Copyright (C) 2016 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file* except in compliance with the License. You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software distributed under the* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied. See the License for the specific language governing* permissions and limitations under the License.*/package com.android.settings.applications.parallelapp;import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.widget.Switch;
import android.util.Log;import androidx.preference.PreferenceGroup;import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.SettingsActivity;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppFilter;import java.util.List;public class ParallelAppSettings extends DashboardFragment {private static final String TAG = "ParallelAppSettings";private static final String KEY = "app_list";private AppFilter mFilter;private int mCloneUserId = -1;private IActivityManager mAms;private UserManager mUm;PreferenceGroup mAppList;@Overridepublic void onAttach(Context context) {super.onAttach(context);mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);mFilter = ApplicationsState.FILTER_ALL_ENABLED;use(ManageParallelAppsPreferenceController.class).setSession(getSettingsLifecycle());use(ManageParallelAppsPreferenceController.class).setParentFragment(this);use(ManageParallelAppsPreferenceController.class).setFilter(mFilter);mAms = ActivityManager.getService();}@Overridepublic void onCreate(Bundle icicle) {super.onCreate(icicle);// 获取布局文件中PreferenceCategory中keymAppList = (PreferenceGroup) findPreference(KEY);android.util.Log.d("xin.wang", " Content of mAppList : " + mAppList.getPreferenceCount());// 视图反射进入ManageParallelAppsPreferenceController,设置setPreferenceGroupuse(ManageParallelAppsPreferenceController.class).setPreferenceGroup(mAppList);}@Overridepublic void onResume() {super.onResume();use(ManageParallelAppsPreferenceController.class).rebuild();}@Overrideprotected String getLogTag() {return TAG;}@Overrideprotected int getPreferenceScreenResId() {return R.xml.parallel_app_settings;}@Overridepublic int getMetricsCategory() {return SettingsEnums.PAGE_UNKNOWN;}
}
user()方法,是父类DashboardFragment的方法
protected <T extends AbstractPreferenceController> T use(Class<T> clazz) {List<AbstractPreferenceController> controllerList = mPreferenceControllers.get(clazz);if (controllerList != null) {if (controllerList.size() > 1) {Log.w(TAG, "Multiple controllers of Class " + clazz.getSimpleName()+ " found, returning first one.");}return (T) controllerList.get(0);}return null;}
进入布局文件parallel_app_settings.xml
<PreferenceScreenxmlns:android="http://schemas.android.com/apk/res/android"xmlns:settings="http://schemas.android.com/apk/res-auto"android:key="parallel_app_settings"android:title="@string/parallel_app"settings:controller="com.android.settings.applications.parallelapp.ManageParallelAppsPreferenceController"settings:searchable="false"><com.android.settingslib.widget.TopIntroPreferenceandroid:key="support_app"android:title="@string/parallel_app_support"/><PreferenceCategoryandroid:key="app_list"android:order="10"settings:searchable="false"></PreferenceCategory></PreferenceScreen>
进入ManageParallelAppsPreferenceController
/** Copyright (C) 2017 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.settings.applications.parallelapp;import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceGroup;import com.android.settings.applications.AppStateBaseBridge;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
import com.android.settingslib.core.lifecycle.Lifecycle;import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;public class ManageParallelAppsPreferenceController extends BasePreferenceController implementsApplicationsState.Callbacks, Preference.OnPreferenceChangeListener {private static final String TAG = "ManageParallelAppsPreferenceController";private final ApplicationsState mApplicationsState;private final PackageInstaller mPackageInstaller;private final PackageManager mPm;private final UserManager mUm;private ApplicationsState.Session mSession;private AppFilter mFilter;private Context mContext;private ParallelAppSettings mParentFragment;private HashSet<String> mSupportAppClone;PreferenceGroup mAppList;public ManageParallelAppsPreferenceController(Context context, String key) {super(context, key);mContext = context;mApplicationsState = ApplicationsState.getInstance((Application) context.getApplicationContext());mPm = mContext.getPackageManager();mPackageInstaller = mPm.getPackageInstaller();mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);//get app could cloning by -> com.tencent.mm=>微信,com.sina.weibo=>微博,com.tencent.mobilqq=>QQ,com.ss.android.ugc.aweme=>抖音// 读取支持手机分身的应用包名String[] supportApps = context.getResources().getStringArray(com.unisoc.internal.R.array.support_app_clone);// 将数组转化为列表mSupportAppClone = new HashSet<String>(Arrays.asList(supportApps));}@Overridepublic int getAvailabilityStatus() {ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);boolean isAdmin = mUm.isAdminUser();boolean isLowRam = activityManager.isLowRamDevice();boolean isSupportAppClone = mContext.getResources().getBoolean(R.bool.config_support_appclone);return isAdmin && !isLowRam && isSupportAppClone ? AVAILABLE : UNSUPPORTED_ON_DEVICE;}@Overridepublic void onRebuildComplete(ArrayList<AppEntry> apps) {if (apps == null) {return;}final Set<String> appsKeySet = new TreeSet<>();final int N = apps.size();android.util.Log.d("xin.wang", "apps.size -> " + N);for (int i = 0; i < N; i++) {final AppEntry entry = apps.get(i);android.util.Log.d("xin.wang", "AppEntry entry --> " + entry.toString());boolean isSupportClone = mSupportAppClone.contains(entry.info.packageName);android.util.Log.d("xin.wang", "get Should Clone for clone --> " + mSupportAppClone);android.util.Log.d("xin.wang", "get app packageName : -> " + entry.info.packageName + "<- isSupportClone ->" + isSupportClone );int userId = UserHandle.getUserId(entry.info.uid);boolean isAdminUser = mUm.isUserAdmin(userId);if (mSupportAppClone.contains(entry.info.packageName) && isAdminUser) {if (!shouldAddPreference(entry)) {continue;}final String prefkey = ParallelAppPreference.generateKey(entry);appsKeySet.add(prefkey);android.util.Log.d("xin.wang", " get appsKeySet -> " + appsKeySet + "<- prefkey ->" + prefkey);// 获取布局中keyParallelAppPreference preference =(ParallelAppPreference) mAppList.findPreference(prefkey);// 当布局为空的时候,实例化一个布局if (preference == null) {preference = new ParallelAppPreference(mContext, entry,mApplicationsState, mUm, mPm, mPackageInstaller);preference.setOnPreferenceChangeListener(this);mAppList.addPreference(preference);} else {preference.updateState();}preference.setOrder(i);}}removeUselessPrefs(appsKeySet);}@Overridepublic void onRunningStateChanged(boolean running) {}@Overridepublic void onPackageListChanged() {rebuild();}@Overridepublic void onPackageIconChanged() {}@Overridepublic void onPackageSizeChanged(String packageName) {}@Overridepublic void onAllSizesComputed() {}@Overridepublic void onLauncherInfoChanged() {}@Overridepublic void onLoadEntriesCompleted() {rebuild();}@Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {if (Utils.isMonkeyRunning()) {return true;}if (preference instanceof ParallelAppPreference) {ParallelAppPreference parAppPreference = (ParallelAppPreference)preference;if ((Boolean)newValue) {parAppPreference.openParApp();} else {parAppPreference.createCloseOptionsDialog(mParentFragment.getActivity());}}return true;}public void setFilter(AppFilter filter) {mFilter = filter;}public void setSession(Lifecycle lifecycle) {mSession = mApplicationsState.newSession(this, lifecycle);}public void setParentFragment(ParallelAppSettings parentFragment) {mParentFragment = parentFragment;}public void setPreferenceGroup(PreferenceGroup appList) {mAppList = appList;android.util.Log.d("xin.wang", "setPreferenceGroup: -> appList -> " + appList.getPreferenceCount());}private boolean shouldAddPreference(AppEntry app) {return app != null && UserHandle.isApp(app.info.uid);}private void removeUselessPrefs(final Set<String> appsKeySet) {final int prefCount = mAppList.getPreferenceCount();android.util.Log.d("xin.wang", "<- prefCount -> " + prefCount);String prefKey;if (prefCount > 0) {for (int i = prefCount - 1; i >= 0; i--) {Preference pref = mAppList.getPreference(i);prefKey = pref.getKey();boolean isEmpty = !appsKeySet.isEmpty();boolean contains = appsKeySet.contains(prefKey);android.util.Log.d("xin.wang", "<- isEmpty -> " + isEmpty + "<- contains -> " + contains + "<- appsKeySet -> " + appsKeySet + "<- preKey ->" + prefKey);if (!appsKeySet.isEmpty() && appsKeySet.contains(prefKey)) {continue;}mAppList.removePreference(pref);}}}public void rebuild() {final ArrayList<AppEntry> apps = mSession.rebuild(mFilter,ApplicationsState.ALPHA_COMPARATOR);boolean flag = apps != null;if (apps != null) {android.util.Log.d("xin.wang", "rebuild: <-- apps -> " + apps.size());onRebuildComplete(apps);}}
}
进入分身的布局ParallelAppPreference.java
点击swicth
开启分身的时候创建一个Toast来提醒
关闭分身弹出dialog,判断是否进行关闭
/** Copyright (C) 2017 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.settings.applications.parallelapp;import android.app.Activity;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.view.View;
import android.widget.Toast;import androidx.appcompat.app.AlertDialog;
import androidx.preference.DialogPreference;
import androidx.preference.PreferenceViewHolder;import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.widget.AppSwitchPreference;import java.util.List;public class ParallelAppPreference extends AppSwitchPreference {private final ApplicationsState mApplicationsState;private final AppEntry mEntry;private final PackageInstaller mPackageInstaller;private final PackageManager mPm;private final String TAG = "ParallelAppPreference";private final UserManager mUm;private Activity mActivity;private Context mContext;public ParallelAppPreference (final Context context, AppEntry entry,ApplicationsState applicationsState, UserManager um, PackageManager pm, PackageInstaller pi) {super(context);mContext = context;setWidgetLayoutResource(R.layout.restricted_switch_widget);mEntry = entry;mEntry.ensureLabel(context);mApplicationsState = applicationsState;mPm = pm;mPackageInstaller = pi;mUm = um;setKey(generateKey(mEntry));if (mEntry.icon == null) {mApplicationsState.ensureIcon(mEntry);}setIcon(mEntry.icon);setTitle(mEntry.label);updateState();}static String generateKey(final AppEntry entry) {android.util.Log.d("xin.wang", "generateKey: -> AppEntry -> " + entry.info.packageName + " | " + entry.info.uid);return entry.info.packageName + "|" + entry.info.uid;}@Overridepublic void onBindViewHolder(PreferenceViewHolder holder) {if (mEntry.icon == null) {holder.itemView.post(new Runnable() {@Overridepublic void run() {mApplicationsState.ensureIcon(mEntry);setIcon(mEntry.icon);}});}final View widgetFrame = holder.findViewById(android.R.id.widget_frame);widgetFrame.setVisibility(View.VISIBLE);super.onBindViewHolder(holder);holder.findViewById(R.id.restricted_icon).setVisibility(View.GONE);holder.findViewById(android.R.id.switch_widget).setVisibility(View.VISIBLE);}// 更新状态public void updateState() {boolean isChecked = false;boolean hasCloneUser = hasCloneUser();try {if (hasCloneUser) {ApplicationInfo info = mPm.getApplicationInfoAsUser(mEntry.info.packageName, PackageManager.GET_META_DATA, getCloneUser());isChecked = (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 ? true : false;android.util.Log.d("xin.wang", "updateState -> 1 -> : " + isChecked);}android.util.Log.d("xin.wang", "updateState -> 2 -> : " + isChecked);setChecked(isChecked);} catch (NameNotFoundException e) {Log.e(TAG,"can not found Application "+mEntry.label);}}//关闭应用分身的时候弹出的dialog -> parallel_app_close -> 当关闭时分身应用的所有数据将会被清除。您确定要继续吗?public void createCloseOptionsDialog(Activity activity) {mActivity = activity;AlertDialog.Builder builder = new AlertDialog.Builder(mContext);builder.setMessage(mContext.getResources().getString(R.string.parallel_app_close));builder.setPositiveButton(android.R.string.ok, mDialogClickListener);builder.setNegativeButton(android.R.string.cancel, mDialogClickListener);AlertDialog dialog = builder.create();dialog.setCancelable(false);dialog.show();}// 开始手机分身public void openParApp() {if (!hasCloneUser()) {createAndStartCloneUser();}try {//应用双开的方法 -> installExistingPackageAsUser(String packageName,int userId);int status = mPm.installExistingPackageAsUser(mEntry.info.packageName, getCloneUser());android.util.Log.d("xin.wang", "openParApp: -> status -> " + status + "<- mEntry.info.packageName -> " + mEntry.info.packageName);if (status == PackageManager.INSTALL_SUCCEEDED) { //PackageManager.INSTALL_SUCCEEDED -> 返回安装成功的状态码 1android.util.Log.d("xin.wang", "openParApp: -> status -> " + status);createToast();} else {Log.d(TAG, "open Parallel App failed status:"+status);}} catch (PackageManager.NameNotFoundException e) {Log.d(TAG, "open Parallel App failed ,"+e);}}// 创建开启分身的Toast -> 分身的 %1$s 已经创建private void createToast() {String content = String.format(mContext.getResources().getString(R.string.parallel_app_open), mEntry.label);Toast.makeText(mContext, content, Toast.LENGTH_LONG).show();}// 获取克隆的用户private int getCloneUser() {int cloneUserId = UserHandle.myUserId();int userCount = 0;List<UserInfo> users = mUm.getUsers(); //UserManager.getUser() -> 获取所有的用户信息for(UserInfo user : users){userCount++;android.util.Log.d("xin.wang", "getCloneUser: users -> " + userCount);if (user.userType != null && user.userType.equals(UserManager.USER_TYPE_PROFILE_CLONE)) {cloneUserId = user.id;}}return cloneUserId;}//判断是否克隆private boolean hasCloneUser() {boolean hasCloneUser = false;List<UserInfo> users = mUm.getUsers(); // UserManager.getUsers() -> 获取所有的用户信息android.util.Log.d("xin.wang", "hasCloneUser: -> user -> " + users + "<- user.size() -> " + users.size());for(UserInfo user : users){android.util.Log.d("xin.wang", "hasCloneUser: -> user.userType -> " + user.userType);if (user.userType != null && user.userType.equals(UserManager.USER_TYPE_PROFILE_CLONE)) {hasCloneUser = true;break;}}return hasCloneUser;}//创建第二个user用户private void createAndStartCloneUser() {String userName = mContext.getResources().getString(R.string.clone_user);UserInfo userInfo = mUm.createProfileForUser(userName, UserManager.USER_TYPE_PROFILE_CLONE,UserInfo.FLAG_PROFILE, UserHandle.USER_SYSTEM);IActivityManager ams = ActivityManager.getService();try {ams.startUserInBackground(userInfo.getUserHandle().getIdentifier());} catch(RemoteException e) {Log.d(TAG, "create and start clone user failed");e.printStackTrace();}}//关闭手机分身的时候弹出的dialogprivate DialogInterface.OnClickListener mDialogClickListener = new DialogInterface.OnClickListener() {public void onClick(DialogInterface arg0, int arg1) {if (arg1 == DialogInterface.BUTTON_NEGATIVE) { //DialogInterface.BUTTON_NEGATIVE -> 取消按钮,点击dialog的取消按钮setChecked(true);} else {//deletePackageAsUser(String packageName,IPackageDeleteObserver observer,int Flags,int userId);mPm.deletePackageAsUser(mEntry.info.packageName, null, 0, getCloneUser());android.util.Log.d("xin.wang", "DialogInterface.OnClickListener -> mEntry.info.packageName -> " + mEntry.info.packageName);}}};
}
4.3、计算应用界面中应用的数量
通过该界面的布局文件app.xml
中的key-> recent_apps_category
进入AppsPreferenceController.java
类中,该类计算当前应用的数量,并且显示当前使用的应用数量
通过分析,可知,该方法中的loadAllAppsCount()
加载所有app的数量,该方法实例化InstalledAppCounter
类,并重写onCountComplete()
方法
/** Copyright (C) 2021 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.settings.applications;import android.app.Application;
import android.app.usage.UsageStats;
import android.content.Context;
import android.icu.text.RelativeDateTimeFormatter;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;import com.android.settings.R;
import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.Utils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.AppPreference;import java.util.List;
import java.util.Map;/*** This controller displays up to four recently used apps.* If there is no recently used app, we only show up an "App Info" preference.*/
public class AppsPreferenceController extends BasePreferenceController implementsLifecycleObserver {public static final int SHOW_RECENT_APP_COUNT = 4;@VisibleForTestingstatic final String KEY_RECENT_APPS_CATEGORY = "recent_apps_category";@VisibleForTestingstatic final String KEY_GENERAL_CATEGORY = "general_category";@VisibleForTestingstatic final String KEY_ALL_APP_INFO = "all_app_infos";@VisibleForTestingstatic final String KEY_SEE_ALL = "see_all_apps";private final ApplicationsState mApplicationsState;private final int mUserId;@VisibleForTestingList<UsageStats> mRecentApps;@VisibleForTestingPreferenceCategory mRecentAppsCategory;@VisibleForTestingPreferenceCategory mGeneralCategory;@VisibleForTestingPreference mAllAppsInfoPref;@VisibleForTestingPreference mSeeAllPref;private Fragment mHost;private boolean mInitialLaunch = false;public AppsPreferenceController(Context context) {super(context, KEY_RECENT_APPS_CATEGORY);mApplicationsState = ApplicationsState.getInstance((Application) mContext.getApplicationContext());mUserId = UserHandle.myUserId();}public void setFragment(Fragment fragment) {mHost = fragment;}@Overridepublic int getAvailabilityStatus() {return AVAILABLE_UNSEARCHABLE;}@Overridepublic void displayPreference(PreferenceScreen screen) {super.displayPreference(screen);initPreferences(screen);refreshUi();mInitialLaunch = true;}@Overridepublic void updateState(Preference preference) {super.updateState(preference);if (!mInitialLaunch) {refreshUi();}}/*** Called when the apps page pauses.*/@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)public void onPause() {mInitialLaunch = false;}@VisibleForTestingvoid refreshUi() {loadAllAppsCount();mRecentApps = loadRecentApps();android.util.Log.d("xin.wang", "mRecentApps -> : " + mRecentApps.size() + "<- loadAllAppsCount -> " + loadRecentApps());if (!mRecentApps.isEmpty()) {displayRecentApps();mAllAppsInfoPref.setVisible(false);mRecentAppsCategory.setVisible(true);mGeneralCategory.setVisible(true);mSeeAllPref.setVisible(true);} else {mAllAppsInfoPref.setVisible(true);mRecentAppsCategory.setVisible(false);mGeneralCategory.setVisible(false);mSeeAllPref.setVisible(false);}}// 计算应用的数量@VisibleForTestingvoid loadAllAppsCount() {// Show total number of installed apps as See all's summary.new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON,mContext.getPackageManager()) {@Overrideprotected void onCountComplete(int num) {if (!mRecentApps.isEmpty()) {mSeeAllPref.setTitle(mContext.getResources().getQuantityString(R.plurals.see_all_apps_title,num, num));android.util.Log.d("xin.wang", "onCountComplete: -> AppsCount -> " + num);} else {mAllAppsInfoPref.setSummary(mContext.getString(R.string.apps_summary, num));}}}.execute();}@VisibleForTestingList<UsageStats> loadRecentApps() {final RecentAppStatsMixin recentAppStatsMixin = new RecentAppStatsMixin(mContext,SHOW_RECENT_APP_COUNT);recentAppStatsMixin.loadDisplayableRecentApps(SHOW_RECENT_APP_COUNT);return recentAppStatsMixin.mRecentApps;}private void initPreferences(PreferenceScreen screen) {mRecentAppsCategory = screen.findPreference(KEY_RECENT_APPS_CATEGORY);mGeneralCategory = screen.findPreference(KEY_GENERAL_CATEGORY);mAllAppsInfoPref = screen.findPreference(KEY_ALL_APP_INFO);mSeeAllPref = screen.findPreference(KEY_SEE_ALL);mRecentAppsCategory.setVisible(false);mGeneralCategory.setVisible(false);mAllAppsInfoPref.setVisible(false);mSeeAllPref.setVisible(false);}private void displayRecentApps() {if (mRecentAppsCategory != null) {final Map<String, Preference> existedAppPreferences = new ArrayMap<>();final int prefCount = mRecentAppsCategory.getPreferenceCount();android.util.Log.d("xin.wang", "<- prefCount -> : " + prefCount);for (int i = 0; i < prefCount; i++) {final Preference pref = mRecentAppsCategory.getPreference(i);final String key = pref.getKey();// android.util.Log.d("xin.wang", "for each key -> : " + key);if (!TextUtils.equals(key, KEY_SEE_ALL)) {existedAppPreferences.put(key, pref);}android.util.Log.d("xin.wang", "<- Map<String,Preference> -> " + existedAppPreferences);}int showAppsCount = 0;android.util.Log.d("xin.wang", "List<UsageStats> mRecentApps -> : " + mRecentApps);for (UsageStats stat : mRecentApps) {final String pkgName = stat.getPackageName();final ApplicationsState.AppEntry appEntry =mApplicationsState.getEntry(pkgName, mUserId);if (appEntry == null) {continue;}boolean rebindPref = true;Preference pref = existedAppPreferences.remove(pkgName);if (pref == null) {pref = new AppPreference(mContext);rebindPref = false;}android.util.Log.d("xin.wang", "pkgName -> : " + pkgName + "<- appEntry.label -> : " + appEntry.label + "<- pref -> " + pref);pref.setKey(pkgName);pref.setTitle(appEntry.label);pref.setIcon(Utils.getBadgedIcon(mContext, appEntry.info));pref.setSummary(StringUtil.formatRelativeTime(mContext,System.currentTimeMillis() - stat.getLastTimeUsed(), false,RelativeDateTimeFormatter.Style.SHORT));pref.setOrder(showAppsCount++);pref.setOnPreferenceClickListener(preference -> {AppInfoBase.startAppInfoFragment(AppInfoDashboardFragment.class,R.string.application_info_label, pkgName, appEntry.info.uid,mHost, 1001 /*RequestCode*/, getMetricsCategory());return true;});if (!rebindPref) {mRecentAppsCategory.addPreference(pref);}}// Remove unused preferences from pref category.for (Preference unusedPref : existedAppPreferences.values()) {mRecentAppsCategory.removePreference(unusedPref);}}}
}
进入超父类AppCounter.java
中
doInBackground
方法进行计算总数量,该计算方法,通过遍历用户和应用双重遍历进行计算,所以算出来的数量double,所以在doInBackground(Void... params)
中,只获取第一个用户,在进行遍历应用
/** Copyright (C) 2016 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file* except in compliance with the License. You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software distributed under the* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied. See the License for the specific language governing* permissions and limitations under the License.*/package com.android.settings.applications;import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.UserHandle;
import android.os.UserManager;import java.util.List;public abstract class AppCounter extends AsyncTask<Void, Void, Integer> {protected final PackageManager mPm;protected final UserManager mUm;public AppCounter(Context context, PackageManager packageManager) {mPm = packageManager;mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);}@Overrideprotected Integer doInBackground(Void... params) {//int count = 0;//int testNum = 0;//int testNum2 = 0;//android.util.Log.d("xin.wang", "doInBackground:UserInfo-> " + testNum);UserInfo user = mUm.getProfiles(UserHandle.myUserId()).get(0);final List<ApplicationInfo> list =mPm.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS| PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS| (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),user.id);for (ApplicationInfo info : list) {//testNum2 ++;//android.util.Log.d("xin.wang", "doInBackground: ApplicationInfo -> " + testNum2);if (includeInCount(info)) {count++;//android.util.Log.d("xin.wang", "doInBackground: includeInCount -> " + count);}}/*for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {testNum ++ ;android.util.Log.d("xin.wang", "doInBackground:UserInfo-> " + testNum);final List<ApplicationInfo> list =mPm.getInstalledApplicationsAsUser(PackageManager.GET_DISABLED_COMPONENTS| PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS| (user.isAdmin() ? PackageManager.MATCH_ANY_USER : 0),user.id);for (ApplicationInfo info : list) {testNum2 ++;android.util.Log.d("xin.wang", "doInBackground: ApplicationInfo -> " + testNum2);if (includeInCount(info)) {count++;android.util.Log.d("xin.wang", "doInBackground: includeInCount -> " + count);}}}*/return count;}@Overrideprotected void onPostExecute(Integer count) {onCountComplete(count);}void executeInForeground() {onPostExecute(doInBackground());}protected abstract void onCountComplete(int num);protected abstract boolean includeInCount(ApplicationInfo info);
}
4.4、进入所有应用
该界面的方法ManageApplications.java
类来显示应用图标和名字
该类有两个内部类适配器
FilterSpinnerAdapter
-> SettingsSpinnerAdapter的适配器
ApplicationsAdapter
-> RecyclerView的适配器
createHeader()
在该方法中创建头部SpinnerHeader,关闭头部的时候只显示一个FILTER_APPS_PERSONAL
的应用
@VisibleForTestingvoid createHeader() {final Activity activity = getActivity();final FrameLayout pinnedHeader = mRootView.findViewById(R.id.pinned_header);mSpinnerHeader = activity.getLayoutInflater().inflate(R.layout.manage_apps_filter_spinner, pinnedHeader, false);mFilterSpinner = mSpinnerHeader.findViewById(R.id.filter_spinner);mFilterAdapter = new FilterSpinnerAdapter(this);mFilterSpinner.setAdapter(mFilterAdapter);mFilterSpinner.setOnItemSelectedListener(this);mFilterSpinner.setVisibility(View.GONE);pinnedHeader.addView(mSpinnerHeader, 0);final AppFilterRegistry appFilterRegistry = AppFilterRegistry.getInstance();final int filterType = appFilterRegistry.getDefaultFilterType(mListType);android.util.Log.d("xin.wang", "createHeader:-> filterType -> " + mListType + "<- filterType -> " + filterType); //filterType = 4 -> FILTER_APPS_ALLandroid.util.Log.d("xin.wang", "createHeader:->mFilterOptions.size()-> " + mFilterAdapter.getCount());//mFilterAdapter.enableFilter(filterType); //filterType = 4 -> FILTER_APPS_ALLif (mListType == LIST_TYPE_MAIN) {if (UserManager.get(getActivity()).getUserProfiles().size() > 1 && !mIsWorkOnly&& !mIsPersonalOnly) {mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL);//mFilterAdapter.enableFilter(FILTER_APPS_WORK);}}if (mListType == LIST_TYPE_NOTIFICATION) {mFilterAdapter.enableFilter(FILTER_APPS_RECENT);mFilterAdapter.enableFilter(FILTER_APPS_FREQUENT);mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED);mFilterAdapter.enableFilter(FILTER_APPS_ALL);}if (mListType == LIST_TYPE_HIGH_POWER) {mFilterAdapter.enableFilter(FILTER_APPS_POWER_ALLOWLIST_ALL);}setCompositeFilter();}
4.5、开启手机分身后所有应用的显示
个人区域存放所有应用
工作区域显示双开的应用
实现的逻辑类ProfileSelectManageApplications
/** Copyright (C) 2019 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.android.settings.dashboard.profileselector;import android.os.Bundle;import androidx.fragment.app.Fragment;import com.android.settings.applications.manageapplications.ManageApplications;/*** Application Setting page for personal/managed profile.*/
public class ProfileSelectManageApplications extends ProfileSelectFragment {@Overridepublic Fragment[] getFragments() {final Bundle workOnly = getArguments() != null ? getArguments().deepCopy() : new Bundle();workOnly.putInt(EXTRA_PROFILE, ProfileSelectFragment.ProfileType.WORK);final Fragment workFragment = new ManageApplications();workFragment.setArguments(workOnly);final Bundle personalOnly = getArguments() != null ? getArguments() : new Bundle();personalOnly.putInt(EXTRA_PROFILE, ProfileSelectFragment.ProfileType.PERSONAL);final Fragment personalFragment = new ManageApplications();personalFragment.setArguments(personalOnly);return new Fragment[]{personalFragment, //0workFragment};}
}
4.6、在Launcher进行设置
开启应用分身时第一次会创建clone用户,在创建clone用户时会触发该用户的下的app的onPackagesUpdated更新操作,而在无抽屉模式下onPackagesUpdated更新会触发VerifyIdleAppTask任务来更新该用户的图标显示
在VerifyIdleAppTask任务中对onPackagesUpdated进行判断,如果为clone用户下的应用更新只更新允许应用分身的package包名其他的应用则进行过滤不显示更新。
路径: Launcher3/src/com/sprd/ext/multimode/VerifyIdleAppTask.java