Merge "Allow attaching multiple unarchivalListeners to a single session." into main
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 0e131b4..43322641 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -672,6 +672,13 @@
public @interface UserActionReason {}
/**
+ * The unarchival status is not set.
+ *
+ * @hide
+ */
+ public static final int UNARCHIVAL_STATUS_UNSET = -1;
+
+ /**
* The unarchival is possible and will commence.
*
* <p> Note that this does not mean that the unarchival has completed. This status should be
@@ -736,6 +743,7 @@
* @hide
*/
@IntDef(value = {
+ UNARCHIVAL_STATUS_UNSET,
UNARCHIVAL_OK,
UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
@@ -2696,8 +2704,6 @@
public int developmentInstallFlags = 0;
/** {@hide} */
public int unarchiveId = -1;
- /** {@hide} */
- public IntentSender unarchiveIntentSender;
private final ArrayMap<String, Integer> mPermissionStates;
@@ -2750,7 +2756,6 @@
applicationEnabledSettingPersistent = source.readBoolean();
developmentInstallFlags = source.readInt();
unarchiveId = source.readInt();
- unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
}
/** {@hide} */
@@ -2785,7 +2790,6 @@
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
ret.developmentInstallFlags = developmentInstallFlags;
ret.unarchiveId = unarchiveId;
- ret.unarchiveIntentSender = unarchiveIntentSender;
return ret;
}
@@ -3495,7 +3499,6 @@
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
pw.printPair("unarchiveId", unarchiveId);
- pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
pw.println();
}
@@ -3540,7 +3543,6 @@
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 376b061..a4af5e7 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -27,6 +27,7 @@
import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
@@ -100,6 +101,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
@@ -210,7 +212,6 @@
return;
}
- // TODO(b/278553670) Add special strings for the delete dialog
mPm.mInstallerService.uninstall(
new VersionedPackage(packageName,
PackageManager.VERSION_CODE_HIGHEST),
@@ -264,7 +265,7 @@
try {
// TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
requestUnarchive(packageName, callerPackageName,
- getOrCreateUnarchiveIntentSender(userId, packageName),
+ getOrCreateLauncherListener(userId, packageName),
UserHandle.of(userId),
false /* showUnarchivalConfirmation= */);
} catch (Throwable t) {
@@ -329,7 +330,7 @@
return true;
}
- private IntentSender getOrCreateUnarchiveIntentSender(int userId, String packageName) {
+ private IntentSender getOrCreateLauncherListener(int userId, String packageName) {
Pair<Integer, String> key = Pair.create(userId, packageName);
synchronized (mLauncherIntentSenders) {
IntentSender intentSender = mLauncherIntentSenders.get(key);
@@ -515,7 +516,6 @@
/**
* 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);
@@ -685,15 +685,14 @@
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.
- // TODO(b/316881759) Allow attaching multiple intentSenders to one session.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
sessionParams,
userId);
if (existingSessionId != PackageInstaller.SessionInfo.INVALID_ID) {
+ attachListenerToSession(statusReceiver, existingSessionId, userId);
return existingSessionId;
}
@@ -702,12 +701,34 @@
installerPackage, mContext.getAttributionTag(),
installerUid,
userId);
+ attachListenerToSession(statusReceiver, sessionId, userId);
+
// TODO(b/297358628) Also cleanup sessions upon device restart.
mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId),
getUnarchiveForegroundTimeout());
return sessionId;
}
+ private void attachListenerToSession(IntentSender statusReceiver, int existingSessionId,
+ int userId) {
+ PackageInstallerSession session = mPm.mInstallerService.getSession(existingSessionId);
+ int status = session.getUnarchivalStatus();
+ // Here we handle a race condition that might happen when an installer reports UNARCHIVAL_OK
+ // but hasn't created a session yet. Without this the listener would never receive a success
+ // response.
+ if (status == UNARCHIVAL_OK) {
+ notifyUnarchivalListener(UNARCHIVAL_OK, session.getInstallerPackageName(),
+ session.params.appPackageName, /* requiredStorageBytes= */ 0,
+ /* userActionIntent= */ null, Set.of(statusReceiver), userId);
+ return;
+ } else if (status != UNARCHIVAL_STATUS_UNSET) {
+ throw new IllegalStateException(TextUtils.formatSimple("Session %s has unarchive status"
+ + "%s but is still active.", session.sessionId, status));
+ }
+
+ session.registerUnarchivalListener(statusReceiver);
+ }
+
/**
* Returns the icon of an archived app. This is the icon of the main activity of the app.
*
@@ -883,13 +904,7 @@
void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
long requiredStorageBytes, @Nullable PendingIntent userActionIntent,
- @Nullable IntentSender unarchiveIntentSender, int userId) {
- if (unarchiveIntentSender == null) {
- // Maybe this can happen if the installer calls reportUnarchivalStatus twice in quick
- // succession.
- return;
- }
-
+ Set<IntentSender> unarchiveIntentSenders, int userId) {
final Intent broadcastIntent = new Intent();
broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName);
broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
@@ -909,15 +924,16 @@
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_DENIED);
- try {
- unarchiveIntentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
- /* handler= */ null, /* requiredPermission= */ null,
- options.toBundle());
- } catch (IntentSender.SendIntentException e) {
- Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
- } finally {
- synchronized (mLauncherIntentSenders) {
- mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
+ for (IntentSender intentSender : unarchiveIntentSenders) {
+ try {
+ intentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
+ /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
+ } catch (IntentSender.SendIntentException e) {
+ Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+ } finally {
+ synchronized (mLauncherIntentSenders) {
+ mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0a23dfb..a9118d4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1759,26 +1759,8 @@
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(
- () -> mPackageArchiver.notifyUnarchivalListener(status,
- session.getInstallerPackageName(),
- session.params.appPackageName, requiredStorageBytes, userActionIntent,
- unarchiveIntentSender, userId));
- session.params.unarchiveIntentSender = null;
- if (status != UNARCHIVAL_OK) {
- Binder.withCleanCallingIdentity(session::abandon);
- }
+ session.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+ userActionIntent);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 4adb60c..117d03f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -22,6 +22,8 @@
import static android.content.pm.DataLoaderType.INCREMENTAL;
import static android.content.pm.DataLoaderType.STREAMING;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET;
import static android.content.pm.PackageItemInfo.MAX_SAFE_LABEL_LENGTH;
import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE;
@@ -65,6 +67,7 @@
import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -97,6 +100,7 @@
import android.content.pm.PackageInstaller.PreapprovalDetails;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageInstaller.UnarchivalStatus;
import android.content.pm.PackageInstaller.UserActionReason;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PackageInfoFlags;
@@ -771,6 +775,10 @@
private final List<String> mResolvedInstructionSets = new ArrayList<>();
@GuardedBy("mLock")
private final List<String> mResolvedNativeLibPaths = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private final Set<IntentSender> mUnarchivalListeners = new ArraySet<>();
+
@GuardedBy("mLock")
private File mInheritedFilesBase;
@GuardedBy("mLock")
@@ -796,6 +804,9 @@
@GuardedBy("mLock")
private int mValidatedTargetSdk = INVALID_TARGET_SDK_VERSION;
+ @UnarchivalStatus
+ private int mUnarchivalStatus = UNARCHIVAL_STATUS_UNSET;
+
private static final FileFilter sAddedApkFilter = new FileFilter() {
@Override
public boolean accept(File file) {
@@ -5088,6 +5099,44 @@
}
}
+ void registerUnarchivalListener(IntentSender intentSender) {
+ synchronized (mLock) {
+ this.mUnarchivalListeners.add(intentSender);
+ }
+ }
+
+ Set<IntentSender> getUnarchivalListeners() {
+ synchronized (mLock) {
+ return new ArraySet<>(mUnarchivalListeners);
+ }
+ }
+
+ void reportUnarchivalStatus(@UnarchivalStatus int status, int unarchiveId,
+ long requiredStorageBytes, PendingIntent userActionIntent) {
+ if (getUnarchivalStatus() != UNARCHIVAL_STATUS_UNSET) {
+ 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));
+ }
+ mUnarchivalStatus = status;
+
+ // Execute expensive calls outside the sync block.
+ mPm.mHandler.post(
+ () -> mPm.mInstallerService.mPackageArchiver.notifyUnarchivalListener(status,
+ getInstallerPackageName(), params.appPackageName, requiredStorageBytes,
+ userActionIntent, getUnarchivalListeners(), userId));
+ if (status != UNARCHIVAL_OK) {
+ Binder.withCleanCallingIdentity(this::abandon);
+ }
+ }
+
+ @UnarchivalStatus
+ int getUnarchivalStatus() {
+ return this.mUnarchivalStatus;
+ }
+
/**
* Free up storage used by this session and its children.
* Must not be called on a child session.
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 e5ecdc4..0403c64 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -210,6 +210,9 @@
anyInt())).thenReturn(1);
when(mInstallerService.getExistingDraftSessionId(anyInt(), any(), anyInt())).thenReturn(
PackageInstaller.SessionInfo.INVALID_ID);
+ PackageInstallerSession session = mock(PackageInstallerSession.class);
+ when(mInstallerService.getSession(anyInt())).thenReturn(session);
+ when(session.getUnarchivalStatus()).thenReturn(PackageInstaller.UNARCHIVAL_STATUS_UNSET);
doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class))))
.when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
eq(mUserId));