diff options
author | 2023-10-31 14:25:21 +0000 | |
---|---|---|
committer | 2023-11-20 18:11:59 +0000 | |
commit | a712057a79c13510d091bf2cc23c651efa83c48b (patch) | |
tree | 09d249a9a18f452baf667bd487a9720e68292508 | |
parent | c02a454ce99f01ad89362fc15ff8db9f52abe189 (diff) |
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
7 files changed, 336 insertions, 16 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 32d252ebda29..275fe7790e84 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3868,8 +3868,9 @@ package android.content.pm { 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 @@ package android.content.pm { 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 59ed0453bc01..1f25fd039dd8 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 @@ interface IPackageInstaller { 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 @@ interface IPackageInstaller { 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 e9a2aaad6579..4f0bfc7437c7 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -387,6 +387,24 @@ public class PackageInstaller { "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 @@ public class PackageInstaller { @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 @@ public class PackageInstaller { * 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 @@ public class PackageInstaller { * <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 @@ public class PackageInstaller { 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 @@ public class PackageInstaller { } } + /** + * 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 class PackageInstaller { public int developmentInstallFlags = 0; /** {@hide} */ public int unarchiveId = -1; + /** {@hide} */ + public IntentSender unarchiveIntentSender; private final ArrayMap<String, Integer> mPermissionStates; @@ -2618,6 +2771,7 @@ public class PackageInstaller { applicationEnabledSettingPersistent = source.readBoolean(); developmentInstallFlags = source.readInt(); unarchiveId = source.readInt(); + unarchiveIntentSender = source.readParcelable(null, IntentSender.class); } /** {@hide} */ @@ -2652,6 +2806,7 @@ public class PackageInstaller { ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent; ret.developmentInstallFlags = developmentInstallFlags; ret.unarchiveId = unarchiveId; + ret.unarchiveIntentSender = unarchiveIntentSender; return ret; } @@ -3364,6 +3519,7 @@ public class PackageInstaller { applicationEnabledSettingPersistent); pw.printHexPair("developmentInstallFlags", developmentInstallFlags); pw.printPair("unarchiveId", unarchiveId); + pw.printPair("unarchiveIntentSender", unarchiveIntentSender); pw.println(); } @@ -3408,6 +3564,7 @@ public class PackageInstaller { 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 eff6157380c0..1a702227c3f3 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 @@ public class PackageArchiver { } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "archiveApp"); + verifyUninstallPermissions(); + CompletableFuture<ArchiveState> archiveStateFuture; try { archiveStateFuture = createArchiveState(packageName, userId); @@ -372,9 +374,11 @@ public class PackageArchiver { 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 @@ public class PackageArchiver { } snapshot.enforceCrossUserPermission(binderUid, userId, true, true, "unarchiveApp"); + verifyInstallPermissions(); + PackageStateInternal ps; try { ps = getPackageState(packageName, snapshot, binderUid, userId); @@ -400,9 +406,12 @@ public class PackageArchiver { 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 @@ public class PackageArchiver { () -> 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 af43a8bec832..c9663fc30ef5 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.BroadcastOptions; 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.InstallConstraints; 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.Handler; 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 class PackageInstallerService extends IPackageInstaller.Stub implements 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + // 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 6f45d2befc6b..f992bd83a8de 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 @@ class PackageManagerShellCommand extends ShellCommand { 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 a9f5b14fc48d..18a2accf071d 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.spy; 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 @@ public class PackageArchiverTest { 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 @@ public class PackageArchiverTest { 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 @@ public class PackageArchiverTest { 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 @@ public class PackageArchiverTest { 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 @@ public class PackageArchiverTest { 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 @@ public class PackageArchiverTest { 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 class PackageArchiverTest { 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); |