Add private profile checks for LauncherApps APIs
Restrict LauncherApps APIs for private profile:
- Caller must hold HOME role (i.e. default launcher)
- Must hold ACCESS_HIDDEN_PROFILES or ACCESS_HIDDEN_PROFILES_FULL
permissions
Remove temporary recents role holder checks.
Test: atest LauncherAppsTest, LauncherAppsForHiddenProfilesTest
Bug: 25851973
Flag: android.multiuser.enable_launcher_apps_hidden_profile_checks
DEVELOPMENT
Change-Id: Icf65b4329bbbd323f521d1f97b5369e7544111c8
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 4b890fa..c7d93bf 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -135,3 +135,10 @@
description: "Allow the use of a profileApiAvailability user property to exclude HIDDEN profiles in API results"
bug: "316362775"
}
+
+flag {
+ name: "enable_launcher_apps_hidden_profile_checks"
+ namespace: "profile_experiences"
+ description: "Enable extra check to limit access to hidden prfiles data in Launcher apps APIs."
+ bug: "321988638"
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0baaff0..83440ec 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -316,6 +316,8 @@
<permission name="android.permission.SET_LOW_POWER_STANDBY_PORTS" />
<permission name="android.permission.MANAGE_ROLLBACKS"/>
<permission name="android.permission.MANAGE_USB"/>
+ <!-- Permission required to test Launcher Apps APIs for hidden profiles -->
+ <permission name="android.permission.ACCESS_HIDDEN_PROFILES_FULL" />
<!-- Needed for tests only -->
<permission name="android.permission.MANAGE_CLOUDSEARCH" />
<permission name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" />
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 84ef6e5..ab631c2 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -250,6 +250,8 @@
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <!-- Permission required to test LauncherApps APIs for hidden profiles -->
+ <uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL" />
<!-- Shell only holds android.permission.NETWORK_SCAN in order to to enable CTS testing -->
<uses-permission android:name="android.permission.NETWORK_SCAN" />
<uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 984a629..295528e 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -38,6 +38,7 @@
import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
+import android.Manifest;
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -52,6 +53,7 @@
import android.app.PendingIntent;
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -84,7 +86,9 @@
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.graphics.Rect;
+import android.multiuser.Flags;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -211,6 +215,7 @@
private final Context mContext;
private final UserManager mUm;
+ private final RoleManager mRoleManager;
private final IPackageManager mIPM;
private final UserManagerInternal mUserManagerInternal;
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
@@ -247,6 +252,7 @@
mContext = context;
mIPM = AppGlobals.getPackageManager();
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mRoleManager = mContext.getSystemService(RoleManager.class);
mUserManagerInternal = Objects.requireNonNull(
LocalServices.getService(UserManagerInternal.class));
mUsageStatsManagerInternal = Objects.requireNonNull(
@@ -451,7 +457,6 @@
private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid,
int targetUserId, String message) {
-
if (targetUserId == callingUserId) return true;
if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) {
return true;
@@ -465,6 +470,14 @@
+ targetUserId + " from " + callingUserId + " not allowed");
return false;
}
+
+ if (areHiddenApisChecksEnabled()
+ && mUm.getUserProperties(UserHandle.of(targetUserId))
+ .getProfileApiVisibility()
+ == UserProperties.PROFILE_API_VISIBILITY_HIDDEN
+ && !canAccessHiddenProfileInjected(callingUid, callingPid)) {
+ return false;
+ }
} finally {
injectRestoreCallingIdentity(ident);
}
@@ -473,10 +486,43 @@
message, true);
}
+ boolean areHiddenApisChecksEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && Flags.enableLauncherAppsHiddenProfileChecks()
+ && Flags.enablePermissionToAccessHiddenProfiles();
+ }
+
private void verifyCallingPackage(String callingPackage) {
verifyCallingPackage(callingPackage, injectBinderCallingUid());
}
+ boolean canAccessHiddenProfileInjected(int callingUid, int callingPid) {
+ AndroidPackage callingPackage = mPackageManagerInternal.getPackage(callingUid);
+ if (callingPackage == null) {
+ return false;
+ }
+
+ if (!mRoleManager
+ .getRoleHoldersAsUser(
+ RoleManager.ROLE_HOME, UserHandle.getUserHandleForUid(callingUid))
+ .contains(callingPackage.getPackageName())) {
+ return false;
+ }
+
+ if (mContext.checkPermission(
+ Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL, callingPid, callingUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ // TODO(b/321988638): add option to disable with a flag
+ return mContext.checkPermission(
+ android.Manifest.permission.ACCESS_HIDDEN_PROFILES,
+ callingPid,
+ callingUid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
@VisibleForTesting // We override it in unit tests
void verifyCallingPackage(String callingPackage, int callerUid) {
int packageUid = -1;
@@ -1566,11 +1612,6 @@
@Override
public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) {
- // Only system launchers, which have access to recents should have access to this API.
- // TODO(b/303803157): Add the new permission check if we decide to have one.
- if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
- throw new SecurityException("Caller is not the recents app");
- }
if (!canAccessProfile(user.getIdentifier(),
"Can't access LauncherUserInfo for another user")) {
return null;
@@ -1585,11 +1626,6 @@
@Override
public List<String> getPreInstalledSystemPackages(UserHandle user) {
- // Only system launchers, which have access to recents should have access to this API.
- // TODO(b/303803157): Update access control for this API to default Launcher app.
- if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
- throw new SecurityException("Caller is not the recents app");
- }
if (!canAccessProfile(user.getIdentifier(),
"Can't access preinstalled packages for another user")) {
return null;
@@ -1610,11 +1646,6 @@
@Override
public @Nullable IntentSender getAppMarketActivityIntent(@NonNull String callingPackage,
@Nullable String packageName, @NonNull UserHandle user) {
- // Only system launchers, which have access to recents should have access to this API.
- // TODO(b/303803157): Update access control for this API to default Launcher app.
- if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
- throw new SecurityException("Caller is not the recents app");
- }
if (!canAccessProfile(user.getIdentifier(),
"Can't access AppMarketActivity for another user")) {
return null;