From 2cbb8d4c15b4ccca318966873aa6124cb80a9fdf Mon Sep 17 00:00:00 2001 From: Jakob Schneider Date: Wed, 6 Dec 2023 14:44:51 +0000 Subject: 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 --- .../com/android/server/pm/PackageArchiver.java | 44 ++++++++++++++++++---- .../com/android/server/pm/PackageArchiverTest.java | 16 ++------ 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index fb311dadd36c..4ba5b768cde0 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -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 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. + * + *

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 diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index 733a43329478..78ced5ab2e41 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -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 -- cgit v1.2.3-59-g8ed1b