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

Commit 736daa47 authored by Jakob Schneider's avatar Jakob Schneider
Browse files

Add support for fetching the icon of an archived app.

Test: PackageInstallerArchiveTest
Bug: 298375566
Change-Id: Iaa171eaf256ae0d1519d667c86440c294fa20fb9
parent 3544524b
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -3355,8 +3355,12 @@ public class ApplicationPackageManager extends PackageManager {
        }
        Drawable dr = null;
        if (itemInfo.packageName != null) {
            if (itemInfo.isArchived) {
                dr = getArchivedAppIcon(itemInfo.packageName);
            } else {
                dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo);
            }
        }
        if (dr == null && itemInfo != appInfo && appInfo != null) {
            dr = loadUnbadgedItemIcon(appInfo, appInfo);
        }
@@ -3964,4 +3968,14 @@ public class ApplicationPackageManager extends PackageManager {
            throw e.rethrowFromSystemServer();
        }
    }

    @Nullable
    private Drawable getArchivedAppIcon(String packageName) {
        try {
            return new BitmapDrawable(null,
                    mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId())));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ package android.content.pm;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedPackageParcel;
@@ -59,7 +60,7 @@ import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.content.IntentSender;
import android.os.UserHandle;

import java.util.Map;

@@ -833,4 +834,6 @@ interface IPackageManager {
    void unregisterPackageMonitorCallback(IRemoteCallback callback);

    ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);

    Bitmap getArchivedAppIcon(String packageName, in UserHandle user);
}
+40 −0
Original line number Diff line number Diff line
@@ -307,6 +307,46 @@ public class PackageArchiver {
        mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage));
    }

    /**
     * Returns the icon of an archived app. This is the icon of the main activity of the app.
     *
     * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
     * launcher activities, only one of the icons is returned arbitrarily.
     */
    public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
        Objects.requireNonNull(packageName);
        Objects.requireNonNull(user);

        Computer snapshot = mPm.snapshotComputer();
        int callingUid = Binder.getCallingUid();
        int userId = user.getIdentifier();
        PackageStateInternal ps;
        try {
            ps = getPackageState(packageName, snapshot, callingUid, userId);
            snapshot.enforceCrossUserPermission(callingUid, userId, true, false,
                    "getArchivedAppIcon");
            verifyArchived(ps, userId);
        } catch (PackageManager.NameNotFoundException e) {
            throw new ParcelableException(e);
        }

        List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault(
                userId).getArchiveState().getActivityInfos();
        if (activityInfos.size() == 0) {
            return null;
        }

        // TODO(b/298452477) Handle monochrome icons.
        // In the rare case the archived app defined more than two launcher activities, we choose
        // the first one arbitrarily.
        return decodeIcon(activityInfos.get(0));
    }

    @VisibleForTesting
    Bitmap decodeIcon(ArchiveActivityInfo archiveActivityInfo) {
        return BitmapFactory.decodeFile(archiveActivityInfo.getIconBitmap().toString());
    }

    private void verifyArchived(PackageStateInternal ps, int userId)
            throws PackageManager.NameNotFoundException {
        PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+5 −0
Original line number Diff line number Diff line
@@ -6389,6 +6389,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService
            return getArchivedPackageInternal(packageName, userId);
        }

        @Override
        public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
            return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user);
        }

        /**
         * Wait for the handler to finish handling all pending messages.
         * @param timeoutMillis Maximum time in milliseconds to wait.
+34 −1
Original line number Diff line number Diff line
@@ -159,11 +159,12 @@ public class PackageArchiverTest {
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
                mock(Resources.class));
        when(mIcon.compress(eq(Bitmap.CompressFormat.PNG), eq(100), any())).thenReturn(true);

        mArchiveManager = spy(new PackageArchiver(mContext, pm));
        doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
                any(LauncherActivityInfo.class), eq(mUserId), anyInt());
        doReturn(mIcon).when(mArchiveManager).decodeIcon(
                any(ArchiveState.ArchiveActivityInfo.class));
    }

    @Test
@@ -374,6 +375,38 @@ public class PackageArchiverTest {
        assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
    }

    @Test
    public void getArchivedAppIcon_packageNotInstalled() {
        when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
                null);

        Exception e = assertThrows(
                ParcelableException.class,
                () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
        assertThat(e.getCause()).hasMessageThat().isEqualTo(
                String.format("Package %s not found.", PACKAGE));
    }

    @Test
    public void getArchivedAppIcon_notArchived() {
        Exception e = assertThrows(
                ParcelableException.class,
                () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
        assertThat(e.getCause()).hasMessageThat().isEqualTo(
                String.format("Package %s is not currently archived.", PACKAGE));
    }

    @Test
    public void getArchivedAppIcon_success() {
        mUserState.setArchiveState(createArchiveState()).setInstalled(false);

        assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo(
                mIcon);
    }


    private static ArchiveState createArchiveState() {
        List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
        for (LauncherActivityInfo mainActivity : createLauncherActivities()) {