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

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

Merge "Add unarchival error dialog." into main

parents 1d6a854d 8388fcbf
Loading
Loading
Loading
Loading
+112 −10
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ 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;
import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
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;
@@ -38,6 +40,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -118,6 +121,15 @@ public class PackageArchiver {

    private static final String ACTION_UNARCHIVE_DIALOG =
            "com.android.intent.action.UNARCHIVE_DIALOG";
    private static final String ACTION_UNARCHIVE_ERROR_DIALOG =
            "com.android.intent.action.UNARCHIVE_ERROR_DIALOG";

    private static final String EXTRA_REQUIRED_BYTES =
            "com.android.content.pm.extra.UNARCHIVE_EXTRA_REQUIRED_BYTES";
    private static final String EXTRA_INSTALLER_PACKAGE_NAME =
            "com.android.content.pm.extra.UNARCHIVE_INSTALLER_PACKAGE_NAME";
    private static final String EXTRA_INSTALLER_TITLE =
            "com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE";

    private final Context mContext;
    private final PackageManagerService mPm;
@@ -305,11 +317,14 @@ public class PackageArchiver {
    /** Creates archived state for the package and user. */
    private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
            throws PackageManager.NameNotFoundException {
        PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
        Computer snapshot = mPm.snapshotComputer();
        PackageStateInternal ps = getPackageState(packageName, snapshot,
                Binder.getCallingUid(), userId);
        verifyNotSystemApp(ps.getFlags());
        String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
        verifyInstaller(responsibleInstallerPackage, userId);
        ApplicationInfo installerInfo = snapshot.getApplicationInfo(
                responsibleInstallerPackage, /* flags= */ 0, userId);
        verifyOptOutStatus(packageName,
                UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId())));

@@ -320,7 +335,7 @@ public class PackageArchiver {
            try {
                archiveState.complete(
                        createArchiveStateInternal(packageName, userId, mainActivities,
                                responsibleInstallerPackage));
                                installerInfo.loadLabel(mContext.getPackageManager()).toString()));
            } catch (IOException e) {
                archiveState.completeExceptionally(e);
            }
@@ -328,8 +343,17 @@ public class PackageArchiver {
        return archiveState;
    }

    static ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage,
    @Nullable
    ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage,
            int userId, String installerPackage) {
        ApplicationInfo installerInfo = mPm.snapshotComputer().getApplicationInfo(
                installerPackage, /* flags= */ 0, userId);
        if (installerInfo == null) {
            // Should never happen because we just fetched the installerInfo.
            Slog.e(TAG, "Couldnt find installer " + installerPackage);
            return null;
        }

        try {
            var packageName = archivedPackage.packageName;
            var mainActivities = archivedPackage.archivedActivities;
@@ -346,7 +370,8 @@ public class PackageArchiver {
                archiveActivityInfos.add(activityInfo);
            }

            return new ArchiveState(archiveActivityInfos, installerPackage);
            return new ArchiveState(archiveActivityInfos,
                    installerInfo.loadLabel(mContext.getPackageManager()).toString());
        } catch (IOException e) {
            Slog.e(TAG, "Failed to create archive state", e);
            return null;
@@ -354,7 +379,7 @@ public class PackageArchiver {
    }

    ArchiveState createArchiveStateInternal(String packageName, int userId,
            List<LauncherActivityInfo> mainActivities, String installerPackage)
            List<LauncherActivityInfo> mainActivities, String installerTitle)
            throws IOException {
        final int iconSize = mContext.getSystemService(
                ActivityManager.class).getLauncherLargeIconSize();
@@ -372,7 +397,7 @@ public class PackageArchiver {
            archiveActivityInfos.add(activityInfo);
        }

        return new ArchiveState(archiveActivityInfos, installerPackage);
        return new ArchiveState(archiveActivityInfos, installerTitle);
    }

    // TODO(b/298452477) Handle monochrome icons.
@@ -412,14 +437,14 @@ public class PackageArchiver {
        return iconFile.toPath();
    }

    private void verifyInstaller(String installerPackage, int userId)
    private void verifyInstaller(String installerPackageName, int userId)
            throws PackageManager.NameNotFoundException {
        if (TextUtils.isEmpty(installerPackage)) {
        if (TextUtils.isEmpty(installerPackageName)) {
            throw new PackageManager.NameNotFoundException("No installer found");
        }
        // Allow shell for easier development.
        if ((Binder.getCallingUid() != Process.SHELL_UID)
                && !verifySupportsUnarchival(installerPackage, userId)) {
                && !verifySupportsUnarchival(installerPackageName, userId)) {
            throw new PackageManager.NameNotFoundException("Installer does not support unarchival");
        }
    }
@@ -588,7 +613,7 @@ public class PackageArchiver {

        final Intent broadcastIntent = new Intent();
        broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
        broadcastIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS,
        broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS,
                PackageInstaller.STATUS_PENDING_USER_ACTION);
        broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
        sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
@@ -782,6 +807,83 @@ public class PackageArchiver {
                : ps.getInstallSource().mUpdateOwnerPackageName;
    }

    void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
            long requiredStorageBytes, @Nullable PendingIntent userActionIntent,
            IntentSender unarchiveIntentSender, int userId) {
        final Intent broadcastIntent = new Intent();
        broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName);
        broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);

        if (status != UNARCHIVAL_OK) {
            final Intent dialogIntent = createErrorDialogIntent(status, installerPackageName,
                    appPackageName,
                    requiredStorageBytes, userActionIntent, userId);
            if (dialogIntent == null) {
                // Error already logged.
                return;
            }
            broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
        }

        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);
        }
    }

    @Nullable
    private Intent createErrorDialogIntent(int status, String installerPackageName,
            String appPackageName,
            long requiredStorageBytes, PendingIntent userActionIntent, int userId) {
        final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_ERROR_DIALOG);
        dialogIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
        if (requiredStorageBytes > 0) {
            dialogIntent.putExtra(EXTRA_REQUIRED_BYTES, requiredStorageBytes);
        }
        // Note that the userActionIntent is provided by the installer and is used only by the
        // system package installer as a follow-up action after the user confirms the dialog.
        if (userActionIntent != null) {
            dialogIntent.putExtra(Intent.EXTRA_INTENT, userActionIntent);
        }
        dialogIntent.putExtra(EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
        // We fetch this label from the archive state because the installer might not be installed
        // anymore in an edge case.
        String installerTitle = getInstallerTitle(appPackageName, userId);
        if (installerTitle == null) {
            // Error already logged.
            return null;
        }
        dialogIntent.putExtra(EXTRA_INSTALLER_TITLE, installerTitle);
        return dialogIntent;
    }

    private String getInstallerTitle(String appPackageName, int userId) {
        PackageStateInternal packageState;
        try {
            packageState = getPackageState(appPackageName,
                    mPm.snapshotComputer(),
                    Process.SYSTEM_UID, userId);
        } catch (PackageManager.NameNotFoundException e) {
            Slog.e(TAG, TextUtils.formatSimple(
                    "notifyUnarchivalListener: Couldn't fetch package state for %s.",
                    appPackageName), e);
            return null;
        }
        ArchiveState archiveState = packageState.getUserStateOrDefault(userId).getArchiveState();
        if (archiveState == null) {
            Slog.e(TAG, TextUtils.formatSimple("notifyUnarchivalListener: App not archived %s.",
                    appPackageName));
            return null;
        }
        return archiveState.getInstallerTitle();
    }

    @NonNull
    private static PackageStateInternal getPackageState(String packageName,
            Computer snapshot, int callingUid, int userId)
+8 −22
Original line number Diff line number Diff line
@@ -16,9 +16,10 @@

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_INSTALLER_DISABLED;
import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED;
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;
@@ -1703,7 +1704,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        });
    }

    // TODO(b/307299702) Implement error dialog and propagate userActionIntent.
    @Override
    public void reportUnarchivalStatus(
            int unarchiveId,
@@ -1746,8 +1746,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements

            // Execute expensive calls outside the sync block.
            mPm.mHandler.post(
                    () -> notifyUnarchivalListener(status, session.params.appPackageName,
                            unarchiveIntentSender));
                    () -> mPackageArchiver.notifyUnarchivalListener(status,
                            session.getInstallerPackageName(),
                            session.params.appPackageName, requiredStorageBytes, userActionIntent,
                            unarchiveIntentSender, userId));
            session.params.unarchiveIntentSender = null;
            if (status != UNARCHIVAL_OK) {
                Binder.withCleanCallingIdentity(session::abandon);
@@ -1776,29 +1778,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
                UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
                UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
                UNARCHIVAL_ERROR_NO_CONNECTIVITY,
                UNARCHIVAL_ERROR_INSTALLER_DISABLED,
                UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED,
                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;
+2 −2
Original line number Diff line number Diff line
@@ -1534,8 +1534,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
            return;
        }
        for (int userId : userIds) {
            var archiveState = PackageArchiver.createArchiveState(archivePackage, userId,
                    responsibleInstallerPackage);
            var archiveState = mInstallerService.mPackageArchiver.createArchiveState(
                    archivePackage, userId, responsibleInstallerPackage);
            if (archiveState == null) {
                continue;
            }
+7 −1
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
@@ -92,6 +93,7 @@ public class PackageArchiverTest {
    private static final String PACKAGE = "com.example";
    private static final String CALLER_PACKAGE = "com.caller";
    private static final String INSTALLER_PACKAGE = "com.installer";
    private static final String INSTALLER_LABEL = "Installer";
    private static final Path ICON_PATH = Path.of("icon.png");

    @Rule
@@ -198,6 +200,10 @@ public class PackageArchiverTest {
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
                mock(Resources.class));
        ApplicationInfo installerAi = mock(ApplicationInfo.class);
        when(mComputer.getApplicationInfo(eq(INSTALLER_PACKAGE), anyLong(), anyInt())).thenReturn(
                installerAi);
        when(installerAi.loadLabel(any())).thenReturn(INSTALLER_LABEL);
        when(mInstallerService.createSessionInternal(any(), any(), any(), anyInt(),
                anyInt())).thenReturn(1);
        when(mInstallerService.getExistingDraftSessionId(anyInt(), any(), anyInt())).thenReturn(
@@ -545,7 +551,7 @@ public class PackageArchiverTest {
                    ICON_PATH, null);
            activityInfos.add(activityInfo);
        }
        return new ArchiveState(activityInfos, INSTALLER_PACKAGE);
        return new ArchiveState(activityInfos, INSTALLER_LABEL);
    }

    private static List<LauncherActivityInfo> createLauncherActivities() {