summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jakob Schneider <jakobschneider@google.com> 2023-11-02 15:55:54 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-11-02 15:55:54 +0000
commit2d3bdff344e3a60b40ade2d6a3d36fd554a846fc (patch)
treea520dadf488d1dbbe9920df30209b3ca00deb87d
parent16952a94a5cc853f1601ef8188c06989870a1f5b (diff)
parent8658235820b3c104313d56c3345f2e922bb6ff81 (diff)
Merge "Add API to check if an app is archiveable." into main
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/ApplicationPackageManager.java10
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/PackageManager.java14
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java66
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java57
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);