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

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

Merge "Store the standard icon during the archive call." into main

parents e175bfb2 6357035a
Loading
Loading
Loading
Loading
+154 −13
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.pm;

import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -24,6 +25,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.Context;
@@ -33,24 +35,37 @@ import android.content.pm.IPackageArchiverService;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageArchiver;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelableException;
import android.os.SELinux;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserStateInternal;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * Responsible archiving apps and returning information about archived apps.
@@ -61,6 +76,8 @@ import java.util.Objects;
 */
public class PackageArchiverService extends IPackageArchiverService.Stub {

    private static final String TAG = "PackageArchiverService";

    /**
     * The maximum time granted for an app store to start a foreground service when unarchival
     * is requested.
@@ -68,6 +85,8 @@ public class PackageArchiverService extends IPackageArchiverService.Stub {
    // TODO(b/297358628) Make this configurable through a flag.
    private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000;

    private static final String ARCHIVE_ICONS_DIR = "package_archiver";

    private final Context mContext;
    private final PackageManagerService mPm;

@@ -97,25 +116,44 @@ public class PackageArchiverService extends IPackageArchiverService.Stub {
        snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                "archiveApp");
        verifyCaller(providedUid, binderUid);
        ArchiveState archiveState;
        CompletableFuture<ArchiveState> archiveStateFuture;
        try {
            archiveState = createArchiveState(packageName, userId);
            archiveStateFuture = createArchiveState(packageName, userId);
        } catch (PackageManager.NameNotFoundException e) {
            Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s",
                    packageName, e.getMessage()));
            throw new ParcelableException(e);
        }

        archiveStateFuture
                .thenAccept(
                        archiveState -> {
                            // TODO(b/282952870) Should be reverted if uninstall fails/cancels
                            try {
                                storeArchiveState(packageName, archiveState, userId);
                            } catch (PackageManager.NameNotFoundException e) {
            throw new ParcelableException(e);
                                sendFailureStatus(intentSender, packageName, e.getMessage());
                                return;
                            }

                            // TODO(b/278553670) Add special strings for the delete dialog
                            mPm.mInstallerService.uninstall(
                new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
                callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
                                    new VersionedPackage(packageName,
                                            PackageManager.VERSION_CODE_HIGHEST),
                                    callerPackageName, DELETE_KEEP_DATA, intentSender, userId,
                                    binderUid);
                        })
                .exceptionally(
                        e -> {
                            sendFailureStatus(intentSender, packageName, e.getMessage());
                            return null;
                        });
    }

    /**
     * Creates archived state for the package and user.
     */
    public ArchiveState createArchiveState(String packageName, int userId)
    public CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
            throws PackageManager.NameNotFoundException {
        PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
                Binder.getCallingUid(), userId);
@@ -123,16 +161,56 @@ public class PackageArchiverService extends IPackageArchiverService.Stub {
        verifyInstaller(responsibleInstallerPackage);

        List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
        final CompletableFuture<ArchiveState> archiveState = new CompletableFuture<>();
        mPm.mHandler.post(() -> {
            try {
                archiveState.complete(
                        createArchiveStateInternal(packageName, userId, mainActivities,
                                responsibleInstallerPackage));
            } catch (IOException e) {
                archiveState.completeExceptionally(e);
            }
        });
        return archiveState;
    }

    private ArchiveState createArchiveStateInternal(String packageName, int userId,
            List<LauncherActivityInfo> mainActivities, String installerPackage)
            throws IOException {
        List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
        for (int i = 0; i < mainActivities.size(); i++) {
            // TODO(b/278553670) Extract and store launcher icons
            LauncherActivityInfo mainActivity = mainActivities.get(i);
            Path iconPath = storeIcon(packageName, mainActivity, userId);
            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
                    mainActivities.get(i).getLabel().toString(),
                    Path.of("/TODO"), null);
                    mainActivity.getLabel().toString(), iconPath, null);
            archiveActivityInfos.add(activityInfo);
        }

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

    // TODO(b/298452477) Handle monochrome icons.
    @VisibleForTesting
    Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
            @UserIdInt int userId)
            throws IOException {
        int iconResourceId = mainActivity.getActivityInfo().getIconResource();
        if (iconResourceId == 0) {
            // The app doesn't define an icon. No need to store anything.
            return null;
        }
        File iconsDir = createIconsDir(userId);
        File iconFile = new File(iconsDir, packageName + "-" + mainActivity.getName() + ".png");
        Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0));
        try (FileOutputStream out = new FileOutputStream(iconFile)) {
            // Note: Quality is ignored for PNGs.
            if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
                throw new IOException(TextUtils.formatSimple("Failure to store icon file %s",
                        iconFile.getName()));
            }
            out.flush();
        }
        return iconFile.toPath();
    }

    private void verifyInstaller(String installerPackage)
@@ -313,6 +391,29 @@ public class PackageArchiverService extends IPackageArchiverService.Stub {
        return ps;
    }

    private void sendFailureStatus(IntentSender statusReceiver, String packageName,
            String message) {
        Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", packageName,
                message));
        final Intent fillIn = new Intent();
        fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
        fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
        fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message);
        try {
            final BroadcastOptions options = BroadcastOptions.makeBasic();
            options.setPendingIntentBackgroundActivityStartMode(
                    MODE_BACKGROUND_ACTIVITY_START_DENIED);
            statusReceiver.sendIntent(mContext, 0, fillIn, /* onFinished= */ null,
                    /* handler= */ null, /* requiredPermission= */ null, options.toBundle());
        } catch (IntentSender.SendIntentException e) {
            Slog.e(
                    TAG,
                    TextUtils.formatSimple("Failed to send failure status for %s with message %s",
                            packageName, message),
                    e);
        }
    }

    private static void verifyCaller(int providedUid, int binderUid) {
        if (providedUid != binderUid) {
            throw new SecurityException(
@@ -323,4 +424,44 @@ public class PackageArchiverService extends IPackageArchiverService.Stub {
                            binderUid));
        }
    }

    private File createIconsDir(@UserIdInt int userId) throws IOException {
        File iconsDir = getIconsDir(userId);
        if (!iconsDir.isDirectory()) {
            iconsDir.delete();
            iconsDir.mkdirs();
            if (!iconsDir.isDirectory()) {
                throw new IOException("Unable to create directory " + iconsDir);
            }
        }
        SELinux.restorecon(iconsDir);
        return iconsDir;
    }

    private File getIconsDir(int userId) {
        return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR);
    }

    private static Bitmap drawableToBitmap(Drawable drawable) {
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();

        }

        Bitmap bitmap;
        if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
            // Needed for drawables that are just a single color.
            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
        } else {
            bitmap =
                    Bitmap.createBitmap(
                            drawable.getIntrinsicWidth(),
                            drawable.getIntrinsicHeight(),
                            Bitmap.Config.ARGB_8888);
        }
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }
}
+12 −2
Original line number Diff line number Diff line
@@ -1240,8 +1240,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
    @Override
    public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
                IntentSender statusReceiver, int userId) {
        uninstall(
                versionedPackage,
                callerPackageName,
                flags,
                statusReceiver,
                userId,
                Binder.getCallingUid());
    }

    void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
            IntentSender statusReceiver, int userId, int callingUid) {
        final Computer snapshot = mPm.snapshotComputer();
        final int callingUid = Binder.getCallingUid();
        snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
        if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
            mAppOps.checkPackage(callingUid, callerPackageName);
@@ -1257,7 +1267,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
        final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
                statusReceiver, versionedPackage.getPackageName(),
                canSilentlyInstallPackage, userId);
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
                    == PackageManager.PERMISSION_GRANTED) {
            // Sweet, call straight through!
            mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
+10 −12
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.content.pm.overlay.OverlayPaths;
import android.os.UserHandle;
import android.os.incremental.IncrementalManager;
import android.service.pm.PackageProto;
import android.service.pm.PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1161,18 +1162,15 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
        for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
            long activityInfoToken = proto.start(
                    PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
            proto.write(PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.TITLE,
                    activityInfo.getTitle());
            proto.write(
                    PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.ICON_BITMAP_PATH,
            proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle());
            if (activityInfo.getIconBitmap() != null) {
                proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH,
                        activityInfo.getIconBitmap().toAbsolutePath().toString());
            proto.write(
                    PackageProto
                            .UserInfoProto
                            .ArchiveState
                            .ArchiveActivityInfo
                            .MONOCHROME_ICON_BITMAP_PATH,
            }
            if (activityInfo.getMonochromeIconBitmap() != null) {
                proto.write(ArchiveActivityInfo.MONOCHROME_ICON_BITMAP_PATH,
                        activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
            }
            proto.end(activityInfoToken);
        }

+7 −4
Original line number Diff line number Diff line
@@ -2062,8 +2062,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
            if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
                String title = parser.getAttributeValue(null,
                        ATTR_ARCHIVE_ACTIVITY_TITLE);
                Path iconPath = Path.of(parser.getAttributeValue(null,
                        ATTR_ARCHIVE_ICON_PATH));
                String iconAttribute = parser.getAttributeValue(null,
                        ATTR_ARCHIVE_ICON_PATH);
                Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute);
                String monochromeAttribute = parser.getAttributeValue(null,
                        ATTR_ARCHIVE_MONOCHROME_ICON_PATH);
                Path monochromeIconPath = monochromeAttribute == null ? null : Path.of(
@@ -2447,8 +2448,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
        for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
            serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
            serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
            if (activityInfo.getIconBitmap() != null) {
                serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
                        activityInfo.getIconBitmap().toAbsolutePath().toString());
            }
            if (activityInfo.getMonochromeIconBitmap() != null) {
                serializer.attribute(null, ATTR_ARCHIVE_MONOCHROME_ICON_PATH,
                        activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+14 −11
Original line number Diff line number Diff line
@@ -56,8 +56,11 @@ public class ArchiveState {
        @NonNull
        private final String mTitle;

        /** The path to the stored icon of the activity in the app's locale. */
        @NonNull
        /**
         * The path to the stored icon of the activity in the app's locale. Null if the app does
         * not define any icon (default icon would be shown on the launcher).
         */
        @Nullable
        private final Path mIconBitmap;

        /** See {@link #mIconBitmap}. Only set if the app defined a monochrome icon. */
@@ -85,21 +88,20 @@ public class ArchiveState {
         * @param title
         *   Corresponds to the activity's android:label in the app's locale.
         * @param iconBitmap
         *   The path to the stored icon of the activity in the app's locale.
         *   The path to the stored icon of the activity in the app's locale. Null if the app does
         *   not define any icon (default icon would be shown on the launcher).
         * @param monochromeIconBitmap
         *   See {@link #mIconBitmap}. Only set if the app defined a monochrome icon.
         */
        @DataClass.Generated.Member
        public ArchiveActivityInfo(
                @NonNull String title,
                @NonNull Path iconBitmap,
                @Nullable Path iconBitmap,
                @Nullable Path monochromeIconBitmap) {
            this.mTitle = title;
            com.android.internal.util.AnnotationValidations.validate(
                    NonNull.class, null, mTitle);
            this.mIconBitmap = iconBitmap;
            com.android.internal.util.AnnotationValidations.validate(
                    NonNull.class, null, mIconBitmap);
            this.mMonochromeIconBitmap = monochromeIconBitmap;

            // onConstructed(); // You can define this method to get a callback
@@ -114,10 +116,11 @@ public class ArchiveState {
        }

        /**
         * The path to the stored icon of the activity in the app's locale.
         * The path to the stored icon of the activity in the app's locale. Null if the app does
         * not define any icon (default icon would be shown on the launcher).
         */
        @DataClass.Generated.Member
        public @NonNull Path getIconBitmap() {
        public @Nullable Path getIconBitmap() {
            return mIconBitmap;
        }

@@ -174,10 +177,10 @@ public class ArchiveState {
        }

        @DataClass.Generated(
                time = 1689169065133L,
                time = 1693590309015L,
                codegenVersion = "1.0.23",
                sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
                inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
                inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
        @Deprecated
        private void __metadata() {}

@@ -292,7 +295,7 @@ public class ArchiveState {
    }

    @DataClass.Generated(
            time = 1689169065144L,
            time = 1693590309027L,
            codegenVersion = "1.0.23",
            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
            inputSignatures = "private final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.ArchiveActivityInfo> mActivityInfos\nprivate final @android.annotation.NonNull java.lang.String mInstallerTitle\nclass ArchiveState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
Loading