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