Android 手机应用

33 篇文章 2 订阅
15 篇文章 2 订阅

Android 手机应用分身

1、实现手机应用分身的方式

当前市面上实现手机分身的方式主要有三类:

  1. 修改Framework -> 使用Android多用户机制进行实现

    该方式适用于手机厂商,修改底层代码,通过创建多用户的方法来实现手机分身功能。

    通过getFileDir()的api发现,在本体得到的是 data/data/com.xxx.xxx,克隆得到的是data/user/10/com.xxx.xxx

  2. 修改apk

    通过反编译apk,修改apk的包名、签名等将apk伪装成另一个app,市面上常见的第三方多开app大部分都是使用该技术。其特点是每次制作一个分身都需要时间进行一个拷贝、并且在应用列表中可以看到

    参考资料:

    https://blog.csdn.net/weixin_43970718/article/details/119116441

  3. 虚拟操作系统

    虚拟一个操作系统,克隆体app在这个虚拟系统中运行,在应用列表不可见,代表产品:360分身大师

    检测方案:
    这个据说是唯一一种绕过getFileDir()的分身方式,确实这种方式让我耗费了很长时间,下面以360的分身大师举例,详细说下分析过程

    1. 首先通过getFileDir()尝试,本体和克隆体输出一致。失败!!
    2. 通过在本体和克隆体登录不同账号获取SP中存储的账号信息,确定获取的信息不同,证明存储位置不一致
    3. 通过文件管理器查看最近修改的文件,发现系统根目录下有一个docker的文件夹,里面包含了跟根目录类似的结构,在其里面的Android/data下就会发现我们自己的包名,分身的本地数据就存放在这里。
    4. 找到分身的本地存储后,本想向其中存储数据用来标记是否为分身,但本体运行时报了一个无权限写入的异常,因此改为了判断当前是否有对该目录的写入权限

2、Android 多用户

2.1、多用户简介

从Android 4.0开始,Google就开始在Android上布局多用户,UserManager因此而诞生。

2.2、基础概念

多用户的一些基础概念:

  1. Uid(用户id):在Linux上,一个用户Uid标识着一个给定的用户。Android上也沿用了Linux用户概念,Root用户Uid为0,System Uid为1000,并且,每个应用程序在安装时也被赋予了单独的Uid,这个Uid将伴随着应用从安装到卸载。
  2. Gid(用户组id):Linux上规定每个应用都应该有一个用户组,对于Android应用程序来说,每个应用的所属用户组与Uid相同。
  3. Gids:应用在安装后所获得权限的Id集合。在Android上,每个权限都可能对应一个或多个group,每个group有个gid name,gids就是通过每个gid name计算得出的id集合,一个UID可以关联GIDS,表明该UID拥有多种权限。

2.3、多户用特性

  1. 独立的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
    
  2. 独立的文件存储

    为了多用户下的数据安全性,在每个新用户创建之初,不管是外部存储(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。

  3. 独立的权限控制

    1. 不同的用户具有权限不同,如:访客用户的默认权限限制就有:

      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等)

      这些权限可以在创建用户时规定,也可以后期由系统动态设置。

    2. 不同用户下App的应用权限是独立的

      前面说到,uid与userId存在一种计算关系(uid = userId * 1000000 + appId),而在系统中对于权限控制也是根据uid和对应的userId来判定的,因此不同用户下相同应用可以具有不同的权限。

  4. App安装的唯一性

    App的文件存储和数据目录在不同用户下都是独立的,但是对于App的安装,多个用户下同一个App却保持着同一个安装目录,即:

    1. 普通三方app:/data/app/

    2. 普通系统应用:/system/app

    3. 特权系统应用:/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" />
    
    1. normal:普通权限,在AndroidManifest.xml中声明就可以获取权限,如 INTERNET 权限
    2. dangerous:敏感权限,需要动态申请告知用户才能获取
    3. singature|privileged:具有系统签名的系统应用才可以获取的权限,对应上方的/system/priv-app

    因此,多用户下的应用其实只安装一次,不同用户下同一个应用的版本和签名都应该相同,不同用户下相同App能够独立运行是因为系统为他们创造了不同的运行环境和权限。

  5. kernel及系统进程的不变性

    在不同用户下,虽然能够看到不同的桌面,不同的运行环境,一切都感觉是新的,但是我们系统本身并没有发生改变,kernel进程、system_server进程以及所有daemon进程依然是同一个,并不会重启。

    而如果我们在不同用户中开启相同的app,我们可以看到可以有多个app进程,而他们的父进程都是同一个,即 zygote:

参考资料:https://blog.csdn.net/misiyuan/article/details/77432020?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ETopNSimilar%7Edefault-1-77432020-blog-94654401.topnsimilarv1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ETopNSimilar%7Edefault-1-77432020-blog-94654401.topnsimilarv1&utm_relevant_index=2

3、UserManager

多用户模型:

framework/base/services/core/java/com/android/server/pm/UserManagerService.java
framework/base/core/java/android/os/UserManager.java
framework/base/core/java/android/content/pm/UserInfo.java
framework/base/core/java/android/os/UserHandle.java

链接:[多用户管理UserManager - Gityuan博客 | 袁辉辉的技术博客](http://gityuan.com/2016/11/20/user_manager/)

3.1、概述

Android 多用户模型,通过UserManagerService对多用户进行创建、删除、查询等管理操作。

  • Binder服务端:UserManagerService继承于IUserManager.Stub,作为 binder 服务端。
  • Binder客户端:UserManager的成员变量mService继承于IUserManager.Stub.Proxy,该变量作为binder客户端。UserManager的大部分核心工作都是交由其成员变量mService,再通过binder调用到UserManagerService所相应的方法。
3.1.1、概念

userId,uid,appid,ShareAppGid概念关系

  • userId:是指用户Id;
  • appId: 是指跟用户空间无关的应用程序id;取值范围 0<= appId <100000
  • uid:是指跟用户空间紧密相关的应用程序id;
  • SharedAppGid:是指可共享的应用id;

转换关系:

uid = userId * 100000  + appId
SharedAppGid = 40000   + appId

另外,PER_USER_RANGE = 100000, 意味着每个user空间最大可以有100000个appid。这些区间分布如下:

  • system appid: [1000, 9999]
  • application appid:[10000, 19999]
  • Shared AppGid: [50000, 59999]
  • isolated appid: [99000, 99999]
3.1.2、UserHandle

常见方法:

方法含义
isSameUser比较两个uid的userId是否相同
isSameApp比较两个uid的appId是否相同
isAppappId是否属于区间[10000,19999]
isIsolatedappId是否属于区间[99000,99999]
getIdentifier获取UserHandle所对应的userId

常见成员变量:(UserHandle的成员变量mHandle便是userId)

userId赋值含义
USER_OWNER0拥有者
USER_ALL-1所有用户
USER_CURRENT-2当前活动用户
USER_CURRENT_OR_SELF-3当前用户或者调用者所在用户
USER_NULL-1000未定义用户

类成员变量:

UserHandle OWNER = new UserHandle(USER_OWNER); // 0
UserHandle ALL = new UserHandle(USER_ALL); // -1
UserHandle CURRENT = new UserHandle(USER_CURRENT); // -2
UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF); // -3

关于UID默认情况下,客户端可使用rocess.myUserHandle(); 服务端可使用UserHandle.getCallingUserId();

3.1.3、UserInfo

UserInfo代表的是一个用户的信息,涉及到的flags及其含义,如下:

flags含义
FLAG_PRIMARY主用户,只有一个user具有该标识
FLAG_ADMIN具有管理特权的用户,例如创建或删除其他用户
FLAG_GUEST访客用户,可能是临时的
FLAG_RESTRICTED限制性用户,较普通用户具有更多限制,例如禁止安装app或者管理wifi等
FLAG_INITIALIZED表明用户已初始化
FLAG_MANAGED_PROFILE表明该用户是另一个用户的轮廓
FLAG_DISABLED表明该用户处于不可用状态
3.1.4、UserState
//用户启动中
public final static int STATE_BOOTING = 0;
//用户正常运行状态
public final static int STATE_RUNNING = 1;
//用户正在停止中
public final static int STATE_STOPPING = 2;
//用户处于关闭状态
public final static int STATE_SHUTDOWN = 3;

用户生命周期线:

STATE_BOOTING -> STATE_RUNNING -> STATE_STOPPING -> STATE_SHUTDOWN.

3.2、UserManager的使用

//获取UserManager
UserManager mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
//获取所有的用户信息
List<UserInfo> users = UserManager.getUsers(true);
//当前用户的id
int myUserId = UserHandle.myUserId();
//根据id获取当前用户的信息
UserInfo myUserInfo = mUserManager.getUserInfo(myUserId);
//用户icon的路径
String iconPath = myUserInfo.iconPath;
//用户icon的Bitmap
Bitmap b = mUserManager.getUserIcon(myUserId);
//当前用户是访客
boolean mIsGuest = myUserInfo.isGuest();
//当前用户是管理员,只有管理员添加或者移除其他用户
boolean mIsAdmin = myUserInfo.isAdmin();
//当前用户是否是受限制的
boolean isRestricted = myUserInfo.isRestricted()//UserManager是否支持切换用户
boolean canSwitchUsers = mUserManager.canSwitchUsers();
 
//循环遍历所有的用户
for (UserInfo user : users) {
    //该用户必须是要支持被切换的
    if (!user.supportsSwitchToByUser()) {
        continue;
    }
    if (user.id == UserHandle.myUserId()) {//当前用户的信息
 
    } else if (user.isGuest()) {//当前用户是否是访客
        continue;
    } else {
        if (user.isAdmin()) {//如果用户是管理,那就显示管理员
            
        }
    }
    //当前用户是否被初始化,如果没有初始化,可以做一次初始化
    if (!isInitialized(user)) {
 
    } else if (user.isRestricted()) {//用户是受限制的
 
    }
}
 
//监听用户的移除,添加和切换
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
context.registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null, mHandler);
 
private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
    @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);

4、逻辑实现

4.1、进入设置中的应用界面逻辑代码

AppDashboardFragment.java,通过getPreferenceScreenResId()进入应用界面的布局文件app.xml

/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.applications;

import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.SearchIndexableResource;

import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** Settings page for apps. */
@SearchIndexable
public class AppDashboardFragment extends DashboardFragment {

    private static final String TAG = "AppDashboardFragment";
    private AppsPreferenceController mAppsPreferenceController;

    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
        final List<AbstractPreferenceController> controllers = new ArrayList<>();
        controllers.add(new AppsPreferenceController(context));
        return controllers;
    }

    @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>

在这里插入图片描述

在这里插入图片描述

4.2、进入手机分身的fragment类

ParallelAppSettings.java

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.android.settings.applications.parallelapp;

import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.widget.Switch;
import android.util.Log;

import androidx.preference.PreferenceGroup;

import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.SettingsActivity;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppFilter;

import java.util.List;

public class ParallelAppSettings extends DashboardFragment {
    private static final String TAG = "ParallelAppSettings";
    private static final String KEY = "app_list";
    private AppFilter mFilter;
    private int mCloneUserId = -1;
    private IActivityManager mAms;
    private UserManager mUm;
    PreferenceGroup mAppList;

    @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);
            }
        }
    };
}

4.3、计算应用界面中应用的数量

在这里插入图片描述

通过该界面的布局文件app.xml中的key-> recent_apps_category

进入AppsPreferenceController.java类中,该类计算当前应用的数量,并且显示当前使用的应用数量

通过分析,可知,该方法中的loadAllAppsCount()加载所有app的数量,该方法实例化InstalledAppCounter类,并重写onCountComplete()方法

/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.applications;

import android.app.Application;
import android.app.usage.UsageStats;
import android.content.Context;
import android.icu.text.RelativeDateTimeFormatter;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;

import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.Utils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.AppPreference;

import java.util.List;
import java.util.Map;

/**
 * This controller displays up to four recently used apps.
 * If there is no recently used app, we only show up an "App Info" preference.
 */
public class AppsPreferenceController extends BasePreferenceController 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);
}

4.4、进入所有应用

在这里插入图片描述

该界面的方法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();
    }

4.5、开启手机分身后所有应用的显示

在这里插入图片描述

个人区域存放所有应用

工作区域显示双开的应用

实现的逻辑类ProfileSelectManageApplications
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.dashboard.profileselector;

import android.os.Bundle;

import androidx.fragment.app.Fragment;

import com.android.settings.applications.manageapplications.ManageApplications;

/**
 * Application Setting page for personal/managed profile.
 */
public class ProfileSelectManageApplications extends ProfileSelectFragment {

    @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
        };
    }
}

4.6、在Launcher进行设置

开启应用分身时第一次会创建clone用户,在创建clone用户时会触发该用户的下的app的onPackagesUpdated更新操作,而在无抽屉模式下onPackagesUpdated更新会触发VerifyIdleAppTask任务来更新该用户的图标显示

在VerifyIdleAppTask任务中对onPackagesUpdated进行判断,如果为clone用户下的应用更新只更新允许应用分身的package包名其他的应用则进行过滤不显示更新。

路径: Launcher3/src/com/sprd/ext/multimode/VerifyIdleAppTask.java

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值