Android15私密空间源码分析

<meta charset="utf-8">

一、概述

私密空间是Android15新特性,涉及了多个模块。本文从设置,框架,Launcher3个模块对私密空间主流程进行分析

二、开始

  • 设置 ->安全和隐私->私密空间

私密空间数据初始化

设置监听开机广播初始化安全数据

public class SafetySourceBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
     ...
        if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
            refreshAllSafetySources(context, EVENT_DEVICE_REBOOTED);
        }
    }

接着PrivateSpaceSafetySource中设置锁屏安全数据

    public static void setSafetySourceData(Context context,
            SafetyEvent safetyEvent) {
      ...
       //判断是否满足了私密空间开启的前置条件
         1:safetyCenter page is enabled
         2:PrivateSpaceFeatures is enabled
         3:userManager.isMainUser()
         // 获取 PrivateSpaceAuthenticationActivity的PendingIntent
         PendingIntent pendingIntent = getPendingIntentForPsDashboard(context);
        //设置隐私安全的title,sunnary,以及 PendingIntent
        SafetySourceStatus status = new SafetySourceStatus.Builder(
                context.getString(R.string.private_space_title),
                context.getString(R.string.private_space_summary),
                SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED)
                .setPendingIntent(pendingIntent).build();
        SafetySourceData safetySourceData =
                new SafetySourceData.Builder().setStatus(status).build();
        Log.d(TAG, "Setting safety source data:"+ Log.getStackTraceString(new Throwable("setSafetySourceData")));
        SafetyCenterManagerWrapper.get().setSafetySourceData(
                context,
                SAFETY_SOURCE_ID,
                safetySourceData,
                safetyEvent
        );

点击私密空间后跳转到PrivateSpaceAuthenticationActivity

PrivateSpaceAuthenticationActivity:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
     ...
     //判断锁屏密码是否设置
        if (getKeyguardManager().isDeviceSecure()) {
            if (savedInstanceState == null) {
            //判断私密空间是否已存在,存在则进行解锁并进入私密空间
                if (mPrivateSpaceMaintainer.doesPrivateSpaceExist()) {
                    unlockAndLaunchPrivateSpaceSettings(this);
                } else {
                //不存在则验证密码并创建私密空间
                    authenticatePrivateSpaceEntry();
                }
            }
        } else {
            //弹窗提示用户设置锁屏密码
            promptToSetDeviceLock();
        }
    }

创建私密空间

PrivateSpaceAuthenticationActivity#authenticatePrivateSpaceEntry中

    private void authenticatePrivateSpaceEntry() {
      //获取私密空间的锁屏凭证意图(Intent)的方法,主要用于管理和访问 Android 中的私密空间。
        Intent credentialIntent = mPrivateSpaceMaintainer.getPrivateProfileLockCredentialIntent();
        if (credentialIntent != null) {
            if (android.multiuser.Flags.usePrivateSpaceIconInBiometricPrompt()) {
                credentialIntent.putExtra(CUSTOM_BIOMETRIC_PROMPT_LOGO_RES_ID_KEY,
                        com.android.internal.R.drawable.stat_sys_private_profile_status);
                credentialIntent.putExtra(CUSTOM_BIOMETRIC_PROMPT_LOGO_DESCRIPTION_KEY,
                        getApplicationContext().getString(
                                com.android.internal.R.string.private_space_biometric_prompt_title
                        ));
            }
            //验证锁屏密码
            mVerifyDeviceLock.launch(credentialIntent);
        } else {
            Log.e(TAG, "verifyCredentialIntent is null even though device lock is set");
            finish();
        }
    }

验证完锁屏密码后执行PrivateSpaceAuthenticationActivity#onLockAuthentication方法

    @VisibleForTesting
    public void onLockAuthentication(Context context) {
        if (mPrivateSpaceMaintainer.doesPrivateSpaceExist()) {
            unlockAndLaunchPrivateSpaceSettings(context);
        } else {
          //私密空间不存在则进入私密空间引导页
            startActivity(new Intent(context, PrivateSpaceSetupActivity.class));
            finish();
        }
    }

[图片上传失败...(image-a1d04-1732513869780)]

PrivateSpaceEducation

    private View.OnClickListener onSetup() {
        return v -> {
            mMetricsFeatureProvider.action(
                    getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_START);
            Log.i(TAG, "Starting private space setup");

            //这里会进入PrivateSpaceCreationFragment进行私密空间的创建
            NavHostFragment.findNavController(PrivateSpaceEducation.this)
                    .navigate(R.id.action_education_to_create);
        };
    }

    private View.OnClickListener onCancel() {
        return v -> {
            Activity activity = getActivity();
            if (activity != null) {
                mMetricsFeatureProvider.action(
                        getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_CANCEL);
                Log.i(TAG, "private space setup cancelled");
                activity.finish();
            }
        };
    }

PrivateSpaceCreationFragment

 //首先会再次判断是否支持私密空间
  @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        if (android.os.Flags.allowPrivateProfile()
                && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
            super.onCreate(savedInstanceState);
        }
    }

    //延迟1s防止阻塞UI,再创建私密空间
    @Override
    public void onResume() {
        super.onResume();
        // Ensures screen visibility to user by introducing a 1-second delay before creating private
        // space.
        sHandler.removeCallbacks(mRunnable);
        sHandler.postDelayed(mRunnable, PRIVATE_SPACE_CREATE_POST_DELAY_MS);
    }

      private Runnable mRunnable =
            () -> {
                createPrivateSpace();
            };

    private void createPrivateSpace() {

        if (PrivateSpaceMaintainer.getInstance(getActivity()).createPrivateSpace()) {
            Log.i(TAG, "Private Space created");
            mMetricsFeatureProvider.action(
                    getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SPACE_CREATED, true);
             //这里判断是否连接网络,有网络就需要登录,无网络就跳转到私密空间密码锁定设置页面
            if (isConnectedToInternet()) {
                registerReceiver();
                sHandler.postDelayed(
                        mAccountLoginRunnable, PRIVATE_SPACE_ACCOUNT_LOGIN_POST_DELAY_MS);
            } else {
                NavHostFragment.findNavController(PrivateSpaceCreationFragment.this)
                        .navigate(R.id.action_set_lock_fragment);
            }
        }
    }

createPrivateSpace源码

  @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public final synchronized boolean createPrivateSpace() {
        if (!Flags.allowPrivateProfile()
                || !android.multiuser.Flags.enablePrivateSpaceFeatures()) {
            return false;
        }
        // Check if Private space already exists
        if (doesPrivateSpaceExist()) {
            return true;
        }
        // a name indicating that the profile was created from the PS Settings page
        final String userName = "Private space";

        if (mUserHandle == null) {
            try {
                mUserHandle = mUserManager.createProfile(
                        userName, USER_TYPE_PROFILE_PRIVATE, new ArraySet<>());
            } catch (Exception e) {
                Log.e(TAG, "Error creating private space", e);
                return false;
            }

            if (mUserHandle == null) {
                Log.e(TAG, "Failed to create private space");
                return false;
            }
            //注册私密空间Intent.ACTION_PROFILE_REMOVED 广播
            registerBroadcastReceiver();
            //启动私密空间
            if (!startProfile()) {
                // TODO(b/333884792): Add test to mock when startProfile fails.
                Log.e(TAG, "profile not started, created profile is deleted");
                deletePrivateSpace();
                return false;
            }

            Log.i(TAG, "Private space created with id: " + mUserHandle.getIdentifier());
            //设置显示设置的入口,设定自动锁定策略,设置隐藏通知
            resetPrivateSpaceSettings();
            //设置完毕
            setUserSetupComplete();
            //私密空间app跳过用户提示
            setSkipFirstUseHints();
            //Disable settings app launcher icon,,,Disable Shortcut picker
            disableComponentsToHidePrivateSpaceSettings();
        }
        return true;
    }

这里梳理一下流程:

1:createPrivateSpace跨进程创建虚拟私密空间,得到UserHandle

2:注册Intent.ACTION_PROFILE_REMOVED广播,当收到广播时移除私密空间配置信息

3:启动后台私密空间,启动失败则删除私密空间

4:初始化私密空间配置

这里可以看到私密空间就是基于多用户,userName为"Private space",userType为USER_TYPE_PROFILE_PRIVATE,如下图所示

[图片上传失败...(image-a2def3-1732513869779)]

初始化私密空间应用

桌面应用的LauncherAppState注册了私密空间的广播监听,前面说到创建多用户完成后会发送Intent.ACTION_PROFILE_ADDED/广播,删除私密空间会发送 Intent.ACTION_PROFILE_REMOVED广播,桌面监听到隐私空间用户创建和销毁的广播后会触发forceReload();

     SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
                .addUserEventListener(mModel::onUserEvent);

     public void onUserEvent(UserHandle user, String action) {
      ...
       else if (UserCache.ACTION_PROFILE_ADDED.equals(action)
                || UserCache.ACTION_PROFILE_REMOVED.equals(action)) {
            forceReload();
        } 
        ...
     }

接着调用到LauncherModel#startLoader方法,最终mLoaderTask的run方法

    private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
      ...
         stopLoader();
                    mLoaderTask = new LoaderTask(
                            mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder);

                    // Always post the loader task, instead of running directly
                    // (even on same thread) so that we exit any nested synchronized blocks
                    MODEL_EXECUTOR.post(mLoaderTask);
    }

接着调用loadAllApps()加载appInfo

   public void run() {
    ...
        allActivityList = loadAllApps();
        ...
     }

遍历私密空间和主空间,mLauncherApps.getActivityList(null, user)获取指定用户的LauncherActivityInfo

    private List<LauncherActivityInfo> loadAllApps() {
        final List<UserHandle> profiles = mUserCache.getUserProfiles();
        List<LauncherActivityInfo> allActivityList = new ArrayList<>();
        // Clear the list of apps
        mBgAllAppsList.clear();

        List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
        boolean isWorkProfileQuiet = false;
        boolean isPrivateProfileQuiet = false;
        for (UserHandle user : profiles) {
            // Query for the set of apps
            final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
            // Fail if we don't have any apps
            // TODO: Fix this. Only fail for the current user.
            if (apps == null || apps.isEmpty()) {
                return allActivityList;
            }
            boolean quietMode = mUserManagerState.isUserQuiet(user);

            if (Flags.enablePrivateSpace()) {
                if (mUserCache.getUserInfo(user).isWork()) {
                    isWorkProfileQuiet = quietMode;
                } else if (mUserCache.getUserInfo(user).isPrivate()) {
                    isPrivateProfileQuiet = quietMode;
                }
            }
         ...

mLauncherApps.getActivityList(null, user) 实现是通过ILauncherApps.getLauncherActivities() Binder调用

    @SuppressLint("RequiresPermission")
    @RequiresPermission(conditional = true,
            anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
    public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
        logErrorForInvalidProfileAccess(user);
        try {
            return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
                    packageName, user), user);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }

接着看system_process实现端LauncherAppsService逻辑,最终generateLauncherActivitiesForArchivedApp通过PackageManagerService获取指定user的launcherActivities,并返回给Launcher

 LauncherAppsService:

  private ParceledListSlice<LauncherActivityInfoInternal> getActivitiesForArchivedApp(
                @Nullable String packageName,
                UserHandle user,
                ParceledListSlice<LauncherActivityInfoInternal> launcherActivities) {
            final List<LauncherActivityInfoInternal> archivedActivities =
                    generateLauncherActivitiesForArchivedApp(packageName, user);
            if (archivedActivities.isEmpty()) {
                return launcherActivities;
            }
            if (launcherActivities == null) {
                return new ParceledListSlice(archivedActivities);
            }
            List<LauncherActivityInfoInternal> result = launcherActivities.getList();
            result.addAll(archivedActivities);
            return new ParceledListSlice(result);
        }

最终Launcher得到私密空间和主空间的最新的appInfo,最后刷新Launcher UI

总结一下:

可以简单理解桌面是个容器,可以加载不同用户空间的apk launcher数据(如主用户,分身用户,Android15新增的私密空间用户)桌面点击不同用户apk,那么这个apk所处的运行环境就是自身用户的环境,得到的私有路径也是私密空间的环境,任务栏中不同应用切换不涉及多用户的切换。

私密空间的应用安装和卸载

点击安装会跳转到Google Play才能安装,这里我们通过adb安装 adb install -t --user USER_ID xx.apk。

本质上跟应用安装流程一样这里不多累赘。主要看看桌面私密空间的刷新逻辑

[图片上传失败...(image-94a445-1732513869779)]

LauncherApps向system_process进程的LauncherAppsService服务注册了一个Binder接口,当应用卸载或者安装时会收到LauncherAppsService的回调

    public void registerCallback(Callback callback, Handler handler) {
        synchronized (this) {
            if (callback != null && findCallbackLocked(callback) < 0) {
                boolean addedFirstCallback = mCallbacks.size() == 0;
                addCallbackLocked(callback, handler);
                if (addedFirstCallback) {
                    try {
                        mService.addOnAppsChangedListener(mContext.getPackageName(),
                                mAppsChangedListener);
                    } catch (RemoteException re) {
                        throw re.rethrowFromSystemServer();
                    }
                }
            }
        }
    }

    private final IOnAppsChangedListener.Stub mAppsChangedListener =
            new IOnAppsChangedListener.Stub() {

        @Override
        public void onPackageRemoved(UserHandle user, String packageName)
                throws RemoteException {
            if (DEBUG) {
                Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName);
            }
            synchronized (LauncherApps.this) {
                for (CallbackMessageHandler callback : mCallbacks) {
                    callback.postOnPackageRemoved(packageName, user);
                }
            }
        }

        @Override
        public void onPackageAdded(UserHandle user, String packageName) throws RemoteException {
            if (DEBUG) {
                Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName);
            }
            synchronized (LauncherApps.this) {
                for (CallbackMessageHandler callback : mCallbacks) {
                    callback.postOnPackageAdded(packageName, user);
                }
            }
        }

最终调用PackageUpdatedTask#execute,安装应用执行appsList.addPackage,卸载应用执行appsList.removePackage,最后刷新Launcher UI

 public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
            @NonNull AllAppsList appsList) {
        final LauncherAppState app = taskController.getApp();
        final Context context = app.getContext();
      ...
        switch (mOp) {
            case OP_ADD: {
                for (int i = 0; i < N; i++) {
                    iconCache.updateIconsForPkg(packages[i], mUser);
                    if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
                        if (DEBUG) {
                            Log.d(TAG, "OP_ADD: PROMISE_APPS_IN_ALL_APPS enabled:"
                                    + " removing promise icon apps from package=" + packages[i]);
                        }
                        appsList.removePackage(packages[i], mUser);
                    }
                    activitiesLists.put(packages[i],
                            appsList.addPackage(context, packages[i], mUser));
                }
                flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
                break;
            }

              case OP_UNAVAILABLE:
                for (int i = 0; i < N; i++) {
                    if (DEBUG) {
                        Log.d(TAG, getOpString() + ": removing package=" + packages[i]);
                    }
                    appsList.removePackage(packages[i], mUser);
                }
                flagOp = FlagOp.NO_OP.addFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
                break;
                ...

删除私密空间

该界面为setting的PrivateSpaceDeleteFragment,点击删除需要验证锁屏密码,然后进入PrivateSpaceDeletionProgressFragment执行真正的删除工作

[图片上传失败...(image-b4aa77-1732513869779)]

  private Runnable mDeletePrivateSpace =
            new Runnable() {
                @Override
                public void run() {
                    deletePrivateSpace();
                    getActivity().finish();
                }
            };

    public void deletePrivateSpace() {
        PrivateSpaceMaintainer.ErrorDeletingPrivateSpace error =
                mPrivateSpaceMaintainer.deletePrivateSpace();
        if (error == DELETE_PS_ERROR_NONE) {
            showSuccessfulDeletionToast();
        } else if (error == DELETE_PS_ERROR_INTERNAL) {
            showDeletionInternalErrorToast();
        }
    }

     public synchronized ErrorDeletingPrivateSpace deletePrivateSpace() {
        if (!doesPrivateSpaceExist()) {
            return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NO_PRIVATE_SPACE;
        }

        try {
            Log.i(TAG, "Deleting Private space with id: " + mUserHandle.getIdentifier());
            if (mUserManager.removeUser(mUserHandle)) {
                Log.i(TAG, "Private space deleted");
                mUserHandle = null;

                return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NONE;
            } else {
                Log.e(TAG, "Failed to delete private space");
            }
        } catch (Exception e) {
            Log.e(TAG, "Error deleting private space", e);
        }
        return ErrorDeletingPrivateSpace.DELETE_PS_ERROR_INTERNAL;
    }   

可以看到是通过UserManager.removeUser() 删除私密空间,桌面收到Intent.ACTION_PROFILE_REMOVED,流程同 Launcher****初始化私密空间应用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容