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****初始化私密空间应用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容