diff options
| author | 2023-11-02 15:55:54 +0000 | |
|---|---|---|
| committer | 2023-11-02 15:55:54 +0000 | |
| commit | 2d3bdff344e3a60b40ade2d6a3d36fd554a846fc (patch) | |
| tree | a520dadf488d1dbbe9920df30209b3ca00deb87d | |
| parent | 16952a94a5cc853f1601ef8188c06989870a1f5b (diff) | |
| parent | 8658235820b3c104313d56c3345f2e922bb6ff81 (diff) | |
Merge "Add API to check if an app is archiveable." into main
7 files changed, 155 insertions, 0 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1cd20f2b1e32..7691c1e8f5f7 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3916,6 +3916,7 @@ package android.content.pm { method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @Deprecated public abstract int installExistingPackage(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") public boolean isAppArchivable(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle); method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 367e92b9d960..ca6d8df1df12 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2586,6 +2586,16 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public boolean isAppArchivable(String packageName) throws NameNotFoundException { + try { + Objects.requireNonNull(packageName); + return mPM.isAppArchivable(packageName, new UserHandle(getUserId())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override public int getMoveStatus(int moveId) { try { return mPM.getMoveStatus(moveId); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 99264150f7d0..babfba13afcd 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -838,4 +838,6 @@ interface IPackageManager { ArchivedPackageParcel getArchivedPackage(in String packageName, int userId); Bitmap getArchivedAppIcon(String packageName, in UserHandle user); + + boolean isAppArchivable(String packageName, in UserHandle user); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index dea4a12541e5..36433ce84618 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -8910,6 +8910,20 @@ public abstract class PackageManager { } /** + * Returns true if an app is archivable. + * + * @throws NameNotFoundException if the given package name is not available to the caller. + * @see PackageInstaller#requestArchive(String, IntentSender) + * + * @hide + */ + @SystemApi + @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) + public boolean isAppArchivable(@NonNull String packageName) throws NameNotFoundException { + throw new UnsupportedOperationException("isAppArchivable not implemented"); + } + + /** * Attempts to clear the user data directory of an application. * Since this may take a little while, the result will * be posted back to the given observer. A deletion will fail if the diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 5cd62872e07b..d5dacce3c8ce 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; import static android.content.pm.ArchivedActivityInfo.drawableToBitmap; @@ -106,6 +107,9 @@ public class PackageArchiver { @Nullable private LauncherApps mLauncherApps; + @Nullable + private AppOpsManager mAppOpsManager; + PackageArchiver(Context context, PackageManagerService mPm) { this.mContext = context; this.mPm = mPm; @@ -178,6 +182,8 @@ public class PackageArchiver { Binder.getCallingUid(), userId); String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); verifyInstaller(responsibleInstallerPackage, userId); + verifyOptOutStatus(packageName, + UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId()))); List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(), userId); @@ -308,6 +314,59 @@ public class PackageArchiver { return intentReceivers != null && !intentReceivers.getList().isEmpty(); } + /** + * Returns true if the app is archivable. + */ + // TODO(b/299299569) Exclude system apps + public boolean isAppArchivable(@NonNull String packageName, @NonNull UserHandle user) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(user); + + Computer snapshot = mPm.snapshotComputer(); + int userId = user.getIdentifier(); + int binderUid = Binder.getCallingUid(); + snapshot.enforceCrossUserPermission(binderUid, userId, true, true, + "isAppArchivable"); + PackageStateInternal ps; + try { + ps = getPackageState(packageName, mPm.snapshotComputer(), + Binder.getCallingUid(), userId); + } catch (PackageManager.NameNotFoundException e) { + throw new ParcelableException(e); + } + + if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) { + return false; + } + + try { + verifyInstaller(getResponsibleInstallerPackage(ps), userId); + getLauncherActivityInfos(packageName, userId); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + + return true; + } + + /** + * Returns true if user has opted the app out of archiving through system settings. + */ + // TODO(b/304256918) Switch this to a separate OP code for archiving. + private boolean isAppOptedOutOfArchiving(String packageName, int uid) { + return Binder.withCleanCallingIdentity(() -> + getAppOpsManager().checkOp(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, + uid, packageName) == MODE_IGNORED); + } + + private void verifyOptOutStatus(String packageName, int uid) + throws PackageManager.NameNotFoundException { + if (isAppOptedOutOfArchiving(packageName, uid)) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("The app %s is opted out of archiving.", packageName)); + } + } + void requestUnarchive( @NonNull String packageName, @NonNull String callerPackageName, @@ -510,6 +569,13 @@ public class PackageArchiver { return mLauncherApps; } + private AppOpsManager getAppOpsManager() { + if (mAppOpsManager == null) { + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + } + return mAppOpsManager; + } + private void storeArchiveState(String packageName, ArchiveState archiveState, int userId) throws PackageManager.NameNotFoundException { synchronized (mPm.mLock) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c9303f2b30a2..638bcbe5822c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -6310,6 +6310,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user); } + @Override + public boolean isAppArchivable(@NonNull String packageName, @NonNull UserHandle user) { + return mInstallerService.mPackageArchiver.isAppArchivable(packageName, user); + } + /** * Wait for the handler to finish handling all pending messages. * @param timeoutMillis Maximum time in milliseconds to wait. diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index 5c8a19c76887..1e65c89643fd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; @@ -103,6 +105,8 @@ public class PackageArchiverTest { @Mock private ActivityManager mActivityManager; @Mock + private AppOpsManager mAppOpsManager; + @Mock private PackageManager mPackageManager; @Mock private PackageInstallerService mInstallerService; @@ -160,12 +164,17 @@ public class PackageArchiverTest { when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState); when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); + when(mContext.getSystemService(AppOpsManager.class)).thenReturn( + mAppOpsManager); when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( mLauncherActivityInfos); when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager); when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100); + when(mAppOpsManager.checkOp( + eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), + anyInt(), eq(PACKAGE))).thenReturn(MODE_ALLOWED); doReturn(mComputer).when(mPackageManagerService).snapshotComputer(); when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn( Binder.getCallingUid()); @@ -305,6 +314,21 @@ public class PackageArchiverTest { } @Test + public void archiveApp_appOptedOutOfArchiving() { + when(mAppOpsManager.checkOp( + eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), + anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED); + + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, + UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + TextUtils.formatSimple("The app %s is opted out of archiving.", PACKAGE)); + } + + @Test public void archiveApp_success() { mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); rule.mocks().getHandler().flush(); @@ -319,6 +343,39 @@ public class PackageArchiverTest { } @Test + public void isAppArchivable_success() throws PackageManager.NameNotFoundException { + assertThat(mArchiveManager.isAppArchivable(PACKAGE, UserHandle.CURRENT)).isTrue(); + } + + @Test + public void isAppArchivable_installerDoesntSupportUnarchival() + throws PackageManager.NameNotFoundException { + doReturn(new ParceledListSlice<>(List.of())) + .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(), + eq(mUserId)); + + assertThat(mArchiveManager.isAppArchivable(PACKAGE, UserHandle.CURRENT)).isFalse(); + } + + @Test + public void isAppArchivable_noMainActivities() throws PackageManager.NameNotFoundException { + when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( + List.of()); + + assertThat(mArchiveManager.isAppArchivable(PACKAGE, UserHandle.CURRENT)).isFalse(); + } + + @Test + public void isAppArchivable_appOptedOutOfArchiving() + throws PackageManager.NameNotFoundException { + when(mAppOpsManager.checkOp( + eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED), + anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED); + + assertThat(mArchiveManager.isAppArchivable(PACKAGE, UserHandle.CURRENT)).isFalse(); + } + + @Test public void unarchiveApp_callerPackageNameIncorrect() { mUserState.setArchiveState(createArchiveState()).setInstalled(false); |