Loading core/java/android/app/ApplicationPackageManager.java +15 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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(); } } } core/java/android/content/pm/IPackageManager.aidl +4 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -833,4 +834,6 @@ interface IPackageManager { void unregisterPackageMonitorCallback(IRemoteCallback callback); ArchivedPackageParcel getArchivedPackage(in String packageName, int userId); Bitmap getArchivedAppIcon(String packageName, in UserHandle user); } services/core/java/com/android/server/pm/PackageArchiver.java +40 −0 Original line number Diff line number Diff line Loading @@ -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); Loading services/core/java/com/android/server/pm/PackageManagerService.java +5 −0 Original line number Diff line number Diff line Loading @@ -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. Loading services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +34 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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()) { Loading Loading
core/java/android/app/ApplicationPackageManager.java +15 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading Loading @@ -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(); } } }
core/java/android/content/pm/IPackageManager.aidl +4 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -833,4 +834,6 @@ interface IPackageManager { void unregisterPackageMonitorCallback(IRemoteCallback callback); ArchivedPackageParcel getArchivedPackage(in String packageName, int userId); Bitmap getArchivedAppIcon(String packageName, in UserHandle user); }
services/core/java/com/android/server/pm/PackageArchiver.java +40 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
services/core/java/com/android/server/pm/PackageManagerService.java +5 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +34 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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()) { Loading