赞
踩
当前市面上实现手机分身的方式主要有三类:
修改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); } }; }
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2020 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. --> <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"> <!-- Placeholder for a list of recent apps --> <!-- See all apps --> <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<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); } @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<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; } @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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。