Add an intentSender to the unarchival API and add onUnarchivalStatus API
with failure handling.
Also add some missing permission checks.
Bug: 308150552
Test: PackageInstallerArchiveTest
Change-Id: I755fe01fd25d5cc405fb30be62e6a9b94ea1d3a3
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 32d252e..275fe77 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3868,8 +3868,9 @@
public class PackageInstaller {
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
- method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3883,12 +3884,20 @@
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID";
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+ field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
field public static final int LOCATION_DATA_APP = 0; // 0x0
field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0
field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1
field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0
}
public static class PackageInstaller.InstallInfo {
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 59ed045..1f25fd0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.app.PendingIntent;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstallerCallback;
@@ -82,7 +83,7 @@
void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
- void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
+ void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)")
void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel,
@@ -90,4 +91,6 @@
in IntentSender statusReceiver,
String installerPackageName, in UserHandle userHandle);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+ void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index e9a2aaa..4f0bfc7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -387,6 +387,24 @@
"android.content.pm.extra.UNARCHIVE_ALL_USERS";
/**
+ * Current status of an unarchive operation. Will be one of
+ * {@link #UNARCHIVAL_OK}, {@link #UNARCHIVAL_ERROR_USER_ACTION_NEEDED},
+ * {@link #UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE}, {@link #UNARCHIVAL_ERROR_NO_CONNECTIVITY},
+ * {@link #UNARCHIVAL_GENERIC_ERROR}, {@link #UNARCHIVAL_ERROR_INSTALLER_DISABLED} or
+ * {@link #UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED}.
+ *
+ * <p> If the status is not {@link #UNARCHIVAL_OK}, then {@link Intent#EXTRA_INTENT} will be set
+ * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a
+ * failure dialog.
+ *
+ * @see #requestUnarchive
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
+
+ /**
* A list of warnings that occurred during installation.
*
* @hide
@@ -652,6 +670,102 @@
@Retention(RetentionPolicy.SOURCE)
public @interface UserActionReason {}
+ /**
+ * The unarchival is possible and will commence.
+ *
+ * <p> Note that this does not mean that the unarchival has completed. This status should be
+ * sent before any longer asynchronous action (e.g. app download) is started.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_OK = 0;
+
+ /**
+ * The user needs to interact with the installer to enable the installation.
+ *
+ * <p> An example use case for this could be that the user needs to login to allow the
+ * download for a paid app.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1;
+
+ /**
+ * Not enough storage to unarchive the application.
+ *
+ * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing
+ * dialog. If no action is provided, then a generic intent
+ * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2;
+
+ /**
+ * The device is not connected to the internet
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3;
+
+ /**
+ * The installer responsible for the unarchival is disabled.
+ *
+ * <p> Should only be used by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
+
+ /**
+ * The installer responsible for the unarchival has been uninstalled
+ *
+ * <p> Should only be used by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
+
+ /**
+ * Generic error: The app cannot be unarchived.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_GENERIC_ERROR = 100;
+
+ /**
+ * The set of error types that can be set for
+ * {@link #reportUnarchivalStatus(int, int, PendingIntent)}.
+ *
+ * @hide
+ */
+ @IntDef(value = {
+ UNARCHIVAL_OK,
+ UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+ UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+ UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+ UNARCHIVAL_ERROR_INSTALLER_DISABLED,
+ UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED,
+ UNARCHIVAL_GENERIC_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnarchivalStatus {}
+
+
/** Default set of checksums - includes all available checksums.
* @see Session#requestChecksums */
private static final int DEFAULT_CHECKSUMS =
@@ -2238,8 +2352,8 @@
* Requests to archive a package which is currently installed.
*
* <p> During the archival process, the apps APKs and cache are removed from the device while
- * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be
- * restored again through their responsible installer.
+ * the user data is kept. Through the {@link #requestUnarchive} call, apps
+ * can be restored again through their responsible installer.
*
* <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
* will be displayed to users with UI treatment to highlight that said apps are archived. If
@@ -2278,6 +2392,10 @@
* <p> The installation will happen asynchronously and can be observed through
* {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
*
+ * @param statusReceiver Callback used to notify whether the installer has accepted the
+ * unarchival request or an error has occurred. The status update will be
+ * sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be
+ * sent.
* @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
* visible to the caller or if the package has no
* installer on the device anymore to unarchive it.
@@ -2290,10 +2408,10 @@
Manifest.permission.REQUEST_INSTALL_PACKAGES})
@SystemApi
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public void requestUnarchive(@NonNull String packageName)
+ public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
throws IOException, PackageManager.NameNotFoundException {
try {
- mInstaller.requestUnarchive(packageName, mInstallerPackageName,
+ mInstaller.requestUnarchive(packageName, mInstallerPackageName, statusReceiver,
new UserHandle(mUserId));
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
@@ -2303,6 +2421,39 @@
}
}
+ /**
+ * Reports the status of an unarchival to the system.
+ *
+ * @param unarchiveId the ID provided by the system as part of the
+ * intent.action.UNARCHIVE broadcast with EXTRA_UNARCHIVE_ID.
+ * @param status is used for the system to provide the user with necessary
+ * follow-up steps or errors.
+ * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field
+ * should be set to specify how many additional bytes of storage
+ * are required to unarchive the app.
+ * @param userActionIntent Optional intent to start a follow up action required to
+ * facilitate the unarchival flow (e.g. user needs to log in).
+ * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES})
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status,
+ long requiredStorageBytes, @Nullable PendingIntent userActionIntent)
+ throws PackageManager.NameNotFoundException {
+ try {
+ mInstaller.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+ userActionIntent, new UserHandle(mUserId));
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// (b/239722738) This class serves as a bridge between the PackageLite class, which
// is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
// This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -2566,6 +2717,8 @@
public int developmentInstallFlags = 0;
/** {@hide} */
public int unarchiveId = -1;
+ /** {@hide} */
+ public IntentSender unarchiveIntentSender;
private final ArrayMap<String, Integer> mPermissionStates;
@@ -2618,6 +2771,7 @@
applicationEnabledSettingPersistent = source.readBoolean();
developmentInstallFlags = source.readInt();
unarchiveId = source.readInt();
+ unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
}
/** {@hide} */
@@ -2652,6 +2806,7 @@
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
ret.developmentInstallFlags = developmentInstallFlags;
ret.unarchiveId = unarchiveId;
+ ret.unarchiveIntentSender = unarchiveIntentSender;
return ret;
}
@@ -3364,6 +3519,7 @@
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
pw.printPair("unarchiveId", unarchiveId);
+ pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
pw.println();
}
@@ -3408,6 +3564,7 @@
dest.writeBoolean(applicationEnabledSettingPersistent);
dest.writeInt(developmentInstallFlags);
dest.writeInt(unarchiveId);
+ dest.writeParcelable(unarchiveIntentSender, flags);
}
public static final Parcelable.Creator<SessionParams>
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index eff6157..1a70222 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -140,6 +140,8 @@
}
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
+ verifyUninstallPermissions();
+
CompletableFuture<ArchiveState> archiveStateFuture;
try {
archiveStateFuture = createArchiveState(packageName, userId);
@@ -372,9 +374,11 @@
void requestUnarchive(
@NonNull String packageName,
@NonNull String callerPackageName,
+ @NonNull IntentSender statusReceiver,
@NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
+ Objects.requireNonNull(statusReceiver);
Objects.requireNonNull(userHandle);
Computer snapshot = mPm.snapshotComputer();
@@ -385,6 +389,8 @@
}
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"unarchiveApp");
+ verifyInstallPermissions();
+
PackageStateInternal ps;
try {
ps = getPackageState(packageName, snapshot, binderUid, userId);
@@ -400,9 +406,12 @@
packageName)));
}
+ // TODO(b/305902395) Introduce a confirmation dialog if the requestor only holds
+ // REQUEST_INSTALL permission.
int draftSessionId;
try {
- draftSessionId = createDraftSession(packageName, installerPackage, userId);
+ draftSessionId = createDraftSession(packageName, installerPackage, statusReceiver,
+ userId);
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -415,11 +424,36 @@
() -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId));
}
- private int createDraftSession(String packageName, String installerPackage, int userId) {
+ private void verifyInstallPermissions() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+ Manifest.permission.REQUEST_INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES "
+ + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request "
+ + "an unarchival.");
+ }
+ }
+
+ private void verifyUninstallPermissions() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+ Manifest.permission.REQUEST_DELETE_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES "
+ + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request "
+ + "an archival.");
+ }
+ }
+
+ private int createDraftSession(String packageName, String installerPackage,
+ IntentSender statusReceiver, int userId) {
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+ sessionParams.unarchiveIntentSender = statusReceiver;
+
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index af43a8b..c9663fc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,8 +16,14 @@
package com.android.server.pm;
+import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
@@ -37,6 +43,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PackageDeleteObserver;
+import android.app.PendingIntent;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -56,6 +63,7 @@
import android.content.pm.PackageInstaller.InstallConstraintsResult;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageInstaller.UnarchivalStatus;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
@@ -71,6 +79,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelableException;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
@@ -1630,8 +1639,10 @@
public void requestUnarchive(
@NonNull String packageName,
@NonNull String callerPackageName,
+ @NonNull IntentSender statusReceiver,
@NonNull UserHandle userHandle) {
- mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle);
+ mPackageArchiver.requestUnarchive(packageName, callerPackageName, statusReceiver,
+ userHandle);
}
@Override
@@ -1689,6 +1700,102 @@
}
}
+ // TODO(b/307299702) Implement error dialog and propagate userActionIntent.
+ @Override
+ public void reportUnarchivalStatus(
+ int unarchiveId,
+ @UnarchivalStatus int status,
+ long requiredStorageBytes,
+ @Nullable PendingIntent userActionIntent,
+ @NonNull UserHandle userHandle) {
+ verifyReportUnarchiveStatusInput(
+ status, requiredStorageBytes, userActionIntent, userHandle);
+
+ int userId = userHandle.getIdentifier();
+ int binderUid = Binder.getCallingUid();
+
+ synchronized (mSessions) {
+ PackageInstallerSession session = mSessions.get(unarchiveId);
+ if (session == null || session.userId != userId
+ || session.params.appPackageName == null) {
+ throw new ParcelableException(new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple(
+ "No valid session with unarchival ID %s found for user %s.",
+ unarchiveId, userId)));
+ }
+
+ if (!isCallingUidOwner(session)) {
+ throw new SecurityException(TextUtils.formatSimple(
+ "The caller UID %s does not have access to the session with unarchiveId "
+ + "%d.",
+ binderUid, unarchiveId));
+ }
+
+ IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender;
+ if (unarchiveIntentSender == null) {
+ throw new IllegalStateException(
+ TextUtils.formatSimple(
+ "Unarchival status for ID %s has already been set or a "
+ + "session has been created for it already by the "
+ + "caller.",
+ unarchiveId));
+ }
+
+ // Execute expensive calls outside the sync block.
+ mPm.mHandler.post(
+ () -> notifyUnarchivalListener(status, session.params.appPackageName,
+ unarchiveIntentSender));
+ session.params.unarchiveIntentSender = null;
+ if (status != UNARCHIVAL_OK) {
+ Binder.withCleanCallingIdentity(session::abandon);
+ }
+ }
+ }
+
+ private static void verifyReportUnarchiveStatusInput(int status, long requiredStorageBytes,
+ @Nullable PendingIntent userActionIntent,
+ @NonNull UserHandle userHandle) {
+ Objects.requireNonNull(userHandle);
+ if (status == UNARCHIVAL_ERROR_USER_ACTION_NEEDED) {
+ Objects.requireNonNull(userActionIntent);
+ }
+ if (status == UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes <= 0) {
+ throw new IllegalStateException(
+ "Insufficient storage error set, but requiredStorageBytes unspecified.");
+ }
+ if (status != UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes > 0) {
+ throw new IllegalStateException(
+ TextUtils.formatSimple("requiredStorageBytes set, but error is %s.", status)
+ );
+ }
+ if (!List.of(
+ UNARCHIVAL_OK,
+ UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+ UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+ UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+ UNARCHIVAL_GENERIC_ERROR).contains(status)) {
+ throw new IllegalStateException("Invalid status code passed " + status);
+ }
+ }
+
+ private void notifyUnarchivalListener(int status, String packageName,
+ IntentSender unarchiveIntentSender) {
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+ fillIn.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, status);
+ // TODO(b/307299702) Attach failure dialog with EXTRA_INTENT and requiredStorageBytes here.
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_DENIED);
+ try {
+ unarchiveIntentSender.sendIntent(mContext, 0, fillIn, /* onFinished= */ null,
+ /* handler= */ null, /* requiredPermission= */ null,
+ options.toBundle());
+ } catch (SendIntentException e) {
+ Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+ }
+ }
+
private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
int installerUid) {
int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 6f45d2b..f992bd8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4689,10 +4689,12 @@
final int translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+ final LocalIntentReceiver receiver = new LocalIntentReceiver();
try {
mInterface.getPackageInstaller().requestUnarchive(packageName,
- /* callerPackageName= */ "", new UserHandle(translatedUserId));
+ /* callerPackageName= */ "", receiver.getIntentSender(),
+ new UserHandle(translatedUserId));
} catch (Exception e) {
pw.println("Failure [" + e.getMessage() + "]");
return 1;
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 a9f5b14..18a2acc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
@@ -171,6 +172,12 @@
when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
+ when(mContext.checkCallingOrSelfPermission(
+ eq(Manifest.permission.REQUEST_INSTALL_PACKAGES))).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ when(mContext.checkCallingOrSelfPermission(
+ eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn(
+ PackageManager.PERMISSION_DENIED);
when(mAppOpsManager.checkOp(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
@@ -386,7 +393,7 @@
Exception e = assertThrows(
SecurityException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, "different",
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
"The UID %s of callerPackageName set by the caller doesn't match the "
@@ -404,7 +411,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
@@ -416,7 +423,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
@@ -428,7 +435,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
@@ -452,7 +459,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("No installer found to unarchive app %s.", PACKAGE));
@@ -462,7 +469,8 @@
public void unarchiveApp_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
- mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+ mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);