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

Commit 2cbb8d4c authored by Jakob Schneider's avatar Jakob Schneider
Browse files

Bugfix: Return an app launcher icon for archived apps across profiles.

This is consistent with how icons can be fetched for apps with APKs, see
comment in PackageArchive.

Test: PackageInstallerArchiveTest
Bug: 314947627
Change-Id: Id4af18815a87838dfbc45d242bf7e3c4fa104d18
parent 6a4fc06f
Loading
Loading
Loading
Loading
+36 −8
Original line number Diff line number Diff line
@@ -649,23 +649,51 @@ public class PackageArchiver {
        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);
            Slog.e(TAG, TextUtils.formatSimple("Package %s couldn't be found.", packageName), e);
            return null;
        }

        List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault(
                userId).getArchiveState().getActivityInfos();
        if (activityInfos.size() == 0) {
        ArchiveState archiveState = getAnyArchiveState(ps, userId);
        if (archiveState == null || archiveState.getActivityInfos().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 includeCloudOverlay(decodeIcon(activityInfos.get(0)));
        return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0)));
    }

    /**
     * This method first checks the ArchiveState for the provided userId and then tries to fallback
     * to other users if the current user is not archived.
     *
     * <p> This fallback behaviour is required for archived apps to fit into the multi-user world
     * where APKs are shared across users. E.g. current ways of fetching icons for apps that are
     * only installed on the work profile also work when executed on the personal profile if you're
     * using {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}. Resource fetching from APKs is for
     * the most part userId-agnostic, which we need to mimic here in order for existing methods
     * like {@link PackageManager#getApplicationIcon} to continue working.
     *
     * @return {@link ArchiveState} for {@code userId} if present. If not present, false back to an
     * arbitrary userId. If no user is archived, returns null.
     */
    @Nullable
    private ArchiveState getAnyArchiveState(PackageStateInternal ps, int userId) {
        PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
        if (isArchived(userState)) {
            return userState.getArchiveState();
        }

        for (int i = 0; i < ps.getUserStates().size(); i++) {
            userState = ps.getUserStates().valueAt(i);
            if (isArchived(userState)) {
                return userState.getArchiveState();
            }
        }

        return null;
    }

    @VisibleForTesting
+4 −12
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import android.os.ParcelableException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
import android.util.SparseArray;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -172,6 +173,7 @@ public class PackageArchiverTest {
        mUserState = new PackageUserStateImpl().setInstalled(true);
        mPackageSetting.setUserState(mUserId, mUserState);
        when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
        when(mPackageState.getUserStates()).thenReturn(new SparseArray<>());

        when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
        when(mContext.getSystemService(AppOpsManager.class)).thenReturn(
@@ -509,22 +511,12 @@ public class PackageArchiverTest {
        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));
        assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
    }

    @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));
        assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
    }

    @Test