Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1e203823 authored by Jakob Schneider's avatar Jakob Schneider Committed by Android (Google) Code Review
Browse files

Merge "Allow attaching multiple unarchivalListeners to a single session." into main

parents eada59ef 8f8d7272
Loading
Loading
Loading
Loading
+8 −6
Original line number Diff line number Diff line
@@ -671,6 +671,13 @@ public class PackageInstaller {
    @Retention(RetentionPolicy.SOURCE)
    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.
     *
@@ -736,6 +743,7 @@ public class PackageInstaller {
     * @hide
     */
    @IntDef(value = {
            UNARCHIVAL_STATUS_UNSET,
            UNARCHIVAL_OK,
            UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
            UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
@@ -2696,8 +2704,6 @@ public class PackageInstaller {
        public int developmentInstallFlags = 0;
        /** {@hide} */
        public int unarchiveId = -1;
        /** {@hide} */
        public IntentSender unarchiveIntentSender;

        private final ArrayMap<String, Integer> mPermissionStates;

@@ -2750,7 +2756,6 @@ public class PackageInstaller {
            applicationEnabledSettingPersistent = source.readBoolean();
            developmentInstallFlags = source.readInt();
            unarchiveId = source.readInt();
            unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
        }

        /** {@hide} */
@@ -2785,7 +2790,6 @@ public class PackageInstaller {
            ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
            ret.developmentInstallFlags = developmentInstallFlags;
            ret.unarchiveId = unarchiveId;
            ret.unarchiveIntentSender = unarchiveIntentSender;
            return ret;
        }

@@ -3495,7 +3499,6 @@ public class PackageInstaller {
                    applicationEnabledSettingPersistent);
            pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
            pw.printPair("unarchiveId", unarchiveId);
            pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
            pw.println();
        }

@@ -3540,7 +3543,6 @@ public class PackageInstaller {
            dest.writeBoolean(applicationEnabledSettingPersistent);
            dest.writeInt(developmentInstallFlags);
            dest.writeInt(unarchiveId);
            dest.writeParcelable(unarchiveIntentSender, flags);
        }

        public static final Parcelable.Creator<SessionParams>
+38 −22
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static android.content.pm.ArchivedActivityInfo.drawableToBitmap;
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.HashMap;
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 @@ public class PackageArchiver {
                                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 @@ public class PackageArchiver {
        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 @@ public class PackageArchiver {
        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 @@ public class PackageArchiver {
    /**
     * 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 @@ public class PackageArchiver {
                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 @@ public class PackageArchiver {
                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 @@ public class PackageArchiver {

    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,10 +924,10 @@ public class PackageArchiver {
        final BroadcastOptions options = BroadcastOptions.makeBasic();
        options.setPendingIntentBackgroundActivityStartMode(
                MODE_BACKGROUND_ACTIVITY_START_DENIED);
        for (IntentSender intentSender : unarchiveIntentSenders) {
            try {
            unarchiveIntentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null,
                    /* handler= */ null, /* requiredPermission= */ null,
                    options.toBundle());
                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 {
@@ -921,6 +936,7 @@ public class PackageArchiver {
                }
            }
        }
    }

    @Nullable
    private Intent createErrorDialogIntent(int status, String installerPackageName,
+2 −20
Original line number Diff line number Diff line
@@ -1759,26 +1759,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                        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);
        }
    }

+49 −0
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDAT
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.AppOpsManager;
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;
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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    @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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
        }
    }

    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.
+3 −0
Original line number Diff line number Diff line
@@ -210,6 +210,9 @@ public class PackageArchiverTest {
                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));