当前市面上实现手机分身的方式主要有三类:
修改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的分身大师举例,详细说下分析过程
从Android 4.0开始,Google就开始在Android上布局多用户,UserManager因此而诞生。
多用户的一些基础概念:
独立的userId
Android在创建每个用户时,都会分配一个整型的userId。对于主用户(正常下的默认用户)来说,userId为0,之后创建的userId将从10开始计算,每增加一个userId加1。
K631:/ # pm list users
Users:
UserInfo{0:机主:c13} running
UserInfo{10:clone:1010} running
创建一个名为“ulangch”的用户:
root@virgo:/ # pm create-user "ulangch"
Success: created user id 11
K631:/ # pm list users
Users:
UserInfo{0:机主:c13} running
UserInfo{10:clone:1010} running
UserInfo{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 obb
K631:/ # ls -l /sdcard
lrw-r--r-- 1 root root 21 2022-09-08 17:44 /sdcard -> /storage/self/primary
K631:/ # 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 11
K631:/ # 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 // 限制配置WiFi
no_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" />
/system/priv-app因此,多用户下的应用其实只安装一次,不同用户下同一个应用的版本和签名都应该相同,不同用户下相同App能够独立运行是因为系统为他们创造了不同的运行环境和权限。
kernel及系统进程的不变性
在不同用户下,虽然能够看到不同的桌面,不同的运行环境,一切都感觉是新的,但是我们系统本身并没有发生改变,kernel进程、system_server进程以及所有daemon进程依然是同一个,并不会重启。
而如果我们在不同用户中开启相同的app,我们可以看到可以有多个app进程,而他们的父进程都是同一个,即 zygote:
多用户模型:
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/)
Android 多用户模型,通过UserManagerService对多用户进行创建、删除、查询等管理操作。
userId,uid,appid,ShareAppGid概念关系
转换关系:
uid = userId * 100000 + appId
SharedAppGid = 40000 + appId
另外,PER_USER_RANGE = 100000, 意味着每个user空间最大可以有100000个appid。这些区间分布如下:
常见方法:
| 方法 | 含义 |
|---|---|
| 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();
UserInfo代表的是一个用户的信息,涉及到的flags及其含义,如下:
| flags | 含义 |
|---|---|
| FLAG_PRIMARY | 主用户,只有一个user具有该标识 |
| FLAG_ADMIN | 具有管理特权的用户,例如创建或删除其他用户 |
| FLAG_GUEST | 访客用户,可能是临时的 |
| FLAG_RESTRICTED | 限制性用户,较普通用户具有更多限制,例如禁止安装app或者管理wifi等 |
| FLAG_INITIALIZED | 表明用户已初始化 |
| FLAG_MANAGED_PROFILE | 表明该用户是另一个用户的轮廓 |
| FLAG_DISABLED | 表明该用户处于不可用状态 |
//用户启动中
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.
//获取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() {
@Override
public 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);
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;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.MANAGE_APPLICATIONS;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
public int getHelpResource() {
return R.string.help_url_apps_and_notifications;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.apps;
}
@Override
public 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);
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context);
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.apps;
return Arrays.asList(sir);
}
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context);
}
};
}
<PreferenceScreen
xmlns: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">
<Preference
android: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"/>
<PreferenceCategory
android:key="recent_apps_category"
android:title="@string/recent_app_category_title"
android:order="-998"
settings:searchable="false">
<Preference
android: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>
<PreferenceCategory
android:key="general_category"
android:title="@string/category_name_general"
android:order="-997"
android:visibility="gone"
settings:searchable="false"/>
<Preference
android: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>
<Preference
android: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>
<PreferenceCategory
android:key="dashboard_tile_placeholder"
android:order="10"/>
<Preference
android: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>
<Preference
android: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"/>
<Preference
android: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>


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;
@Override
public 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();
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 获取布局文件中PreferenceCategory中key
mAppList = (PreferenceGroup) findPreference(KEY);
android.util.Log.d("xin.wang", " Content of mAppList : " + mAppList.getPreferenceCount());
// 视图反射进入ManageParallelAppsPreferenceController,设置setPreferenceGroup
use(ManageParallelAppsPreferenceController.class).setPreferenceGroup(mAppList);
}
@Override
public void onResume() {
super.onResume();
use(ManageParallelAppsPreferenceController.class).rebuild();
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.parallel_app_settings;
}
@Override
public 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
<PreferenceScreen
xmlns: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.TopIntroPreference
android:key="support_app"
android:title="@string/parallel_app_support"/>
<PreferenceCategory
android: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 implements
ApplicationsState.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));
}
@Override
public 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;
}
@Override
public 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);
// 获取布局中key
ParallelAppPreference 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);
}
@Override
public void onRunningStateChanged(boolean running) {}
@Override
public void onPackageListChanged() {
rebuild();
}
@Override
public void onPackageIconChanged() {}
@Override
public void onPackageSizeChanged(String packageName) {}
@Override
public void onAllSizesComputed() {}
@Override
public void onLauncherInfoChanged() {}
@Override
public void onLoadEntriesCompleted() {
rebuild();
}
@Override
public 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;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
if (mEntry.icon == null) {
holder.itemView.post(new Runnable() {
@Override
public 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 -> 返回安装成功的状态码 1
android.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();
}
}
//关闭手机分身的时候弹出的dialog
private 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);
}
}
};
}

通过该界面的布局文件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 implements
LifecycleObserver {
public static final int SHOW_RECENT_APP_COUNT = 4;
@VisibleForTesting
static final String KEY_RECENT_APPS_CATEGORY = "recent_apps_category";
@VisibleForTesting
static final String KEY_GENERAL_CATEGORY = "general_category";
@VisibleForTesting
static final String KEY_ALL_APP_INFO = "all_app_infos";
@VisibleForTesting
static final String KEY_SEE_ALL = "see_all_apps";
private final ApplicationsState mApplicationsState;
private final int mUserId;
@VisibleForTesting
List<UsageStats> mRecentApps;
@VisibleForTesting
PreferenceCategory mRecentAppsCategory;
@VisibleForTesting
PreferenceCategory mGeneralCategory;
@VisibleForTesting
Preference mAllAppsInfoPref;
@VisibleForTesting
Preference 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;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE_UNSEARCHABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
initPreferences(screen);
refreshUi();
mInitialLaunch = true;
}
@Override
public 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;
}
@VisibleForTesting
void 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);
}
}
// 计算应用的数量
@VisibleForTesting
void loadAllAppsCount() {
// Show total number of installed apps as See all's summary.
new InstalledAppCounter(mContext, InstalledAppCounter.IGNORE_INSTALL_REASON,
mContext.getPackageManager()) {
@Override
protected 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();
}
@VisibleForTesting
List<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 -> " + existedAppPreferences);
}
int showAppsCount = 0;
android.util.Log.d("xin.wang", "List 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);
}
@Override
protected 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 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;
}
@Override
protected void onPostExecute(Integer count) {
onCountComplete(count);
}
void executeInForeground() {
onPostExecute(doInBackground());
}
protected abstract void onCountComplete(int num);
protected abstract boolean includeInCount(ApplicationInfo info);
}

该界面的方法ManageApplications.java类来显示应用图标和名字
该类有两个内部类适配器
FilterSpinnerAdapter-> SettingsSpinnerAdapter的适配器

ApplicationsAdapter-> RecyclerView的适配器

createHeader()在该方法中创建头部SpinnerHeader,关闭头部的时候只显示一个FILTER_APPS_PERSONAL的应用
@VisibleForTesting
void 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_ALL
android.util.Log.d("xin.wang", "createHeader:->mFilterOptions.size()-> " + mFilterAdapter.getCount());
//mFilterAdapter.enableFilter(filterType); //filterType = 4 -> FILTER_APPS_ALL
if (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();
}

工作区域显示双开的应用
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 {
@Override
public 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, //0
workFragment
};
}
}
开启应用分身时第一次会创建clone用户,在创建clone用户时会触发该用户的下的app的onPackagesUpdated更新操作,而在无抽屉模式下onPackagesUpdated更新会触发VerifyIdleAppTask任务来更新该用户的图标显示
在VerifyIdleAppTask任务中对onPackagesUpdated进行判断,如果为clone用户下的应用更新只更新允许应用分身的package包名其他的应用则进行过滤不显示更新。
路径: Launcher3/src/com/sprd/ext/multimode/VerifyIdleAppTask.java

