Android15私密空间源码分析

Android15私密空间源码分析

一、概述

私密空间是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 loadAllApps() {

final List profiles = mUserCache.getUserProfiles();

List allActivityList = new ArrayList<>();

// Clear the list of apps

mBgAllAppsList.clear();

List> iconRequestInfos = new ArrayList<>();

boolean isWorkProfileQuiet = false;

boolean isPrivateProfileQuiet = false;

for (UserHandle user : profiles) {

// Query for the set of apps

final List 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 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 getActivitiesForArchivedApp(

@Nullable String packageName,

UserHandle user,

ParceledListSlice launcherActivities) {

final List archivedActivities =

generateLauncherActivitiesForArchivedApp(packageName, user);

if (archivedActivities.isEmpty()) {

return launcherActivities;

}

if (launcherActivities == null) {

return new ParceledListSlice(archivedActivities);

}

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

更多创意

什么是难产
万博365.1

什么是难产

📅 08-22 🔥 3345
设备管理系统证书有哪些
bet3365西甲

设备管理系统证书有哪些

📅 09-23 🔥 6542
excel中怎么快速筛选整数
Bet—288365

excel中怎么快速筛选整数

📅 08-10 🔥 297