diff options
4 files changed, 129 insertions, 35 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..9f072f932a95 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -24,6 +24,8 @@ import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; import static android.content.pm.ArchivedActivityInfo.drawableToBitmap; +import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS; +import static android.content.pm.PackageInstaller.UNARCHIVAL_OK; import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; @@ -38,6 +40,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.IIntentReceiver; @@ -118,6 +121,15 @@ public class PackageArchiver { private static final String ACTION_UNARCHIVE_DIALOG = "com.android.intent.action.UNARCHIVE_DIALOG"; + private static final String ACTION_UNARCHIVE_ERROR_DIALOG = + "com.android.intent.action.UNARCHIVE_ERROR_DIALOG"; + + private static final String EXTRA_REQUIRED_BYTES = + "com.android.content.pm.extra.UNARCHIVE_EXTRA_REQUIRED_BYTES"; + private static final String EXTRA_INSTALLER_PACKAGE_NAME = + "com.android.content.pm.extra.UNARCHIVE_INSTALLER_PACKAGE_NAME"; + private static final String EXTRA_INSTALLER_TITLE = + "com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE"; private final Context mContext; private final PackageManagerService mPm; @@ -305,11 +317,14 @@ public class PackageArchiver { /** Creates archived state for the package and user. */ private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId) throws PackageManager.NameNotFoundException { - PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(), + Computer snapshot = mPm.snapshotComputer(); + PackageStateInternal ps = getPackageState(packageName, snapshot, Binder.getCallingUid(), userId); verifyNotSystemApp(ps.getFlags()); String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); verifyInstaller(responsibleInstallerPackage, userId); + ApplicationInfo installerInfo = snapshot.getApplicationInfo( + responsibleInstallerPackage, /* flags= */ 0, userId); verifyOptOutStatus(packageName, UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId()))); @@ -320,7 +335,7 @@ public class PackageArchiver { try { archiveState.complete( createArchiveStateInternal(packageName, userId, mainActivities, - responsibleInstallerPackage)); + installerInfo.loadLabel(mContext.getPackageManager()).toString())); } catch (IOException e) { archiveState.completeExceptionally(e); } @@ -328,8 +343,17 @@ public class PackageArchiver { return archiveState; } - static ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage, + @Nullable + ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage, int userId, String installerPackage) { + ApplicationInfo installerInfo = mPm.snapshotComputer().getApplicationInfo( + installerPackage, /* flags= */ 0, userId); + if (installerInfo == null) { + // Should never happen because we just fetched the installerInfo. + Slog.e(TAG, "Couldnt find installer " + installerPackage); + return null; + } + try { var packageName = archivedPackage.packageName; var mainActivities = archivedPackage.archivedActivities; @@ -346,7 +370,8 @@ public class PackageArchiver { archiveActivityInfos.add(activityInfo); } - return new ArchiveState(archiveActivityInfos, installerPackage); + return new ArchiveState(archiveActivityInfos, + installerInfo.loadLabel(mContext.getPackageManager()).toString()); } catch (IOException e) { Slog.e(TAG, "Failed to create archive state", e); return null; @@ -354,7 +379,7 @@ public class PackageArchiver { } ArchiveState createArchiveStateInternal(String packageName, int userId, - List<LauncherActivityInfo> mainActivities, String installerPackage) + List<LauncherActivityInfo> mainActivities, String installerTitle) throws IOException { final int iconSize = mContext.getSystemService( ActivityManager.class).getLauncherLargeIconSize(); @@ -372,7 +397,7 @@ public class PackageArchiver { archiveActivityInfos.add(activityInfo); } - return new ArchiveState(archiveActivityInfos, installerPackage); + return new ArchiveState(archiveActivityInfos, installerTitle); } // TODO(b/298452477) Handle monochrome icons. @@ -412,14 +437,14 @@ public class PackageArchiver { return iconFile.toPath(); } - private void verifyInstaller(String installerPackage, int userId) + private void verifyInstaller(String installerPackageName, int userId) throws PackageManager.NameNotFoundException { - if (TextUtils.isEmpty(installerPackage)) { + if (TextUtils.isEmpty(installerPackageName)) { throw new PackageManager.NameNotFoundException("No installer found"); } // Allow shell for easier development. if ((Binder.getCallingUid() != Process.SHELL_UID) - && !verifySupportsUnarchival(installerPackage, userId)) { + && !verifySupportsUnarchival(installerPackageName, userId)) { throw new PackageManager.NameNotFoundException("Installer does not support unarchival"); } } @@ -588,7 +613,7 @@ public class PackageArchiver { final Intent broadcastIntent = new Intent(); broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); - broadcastIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, + broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION); broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent); sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent); @@ -782,6 +807,83 @@ public class PackageArchiver { : ps.getInstallSource().mUpdateOwnerPackageName; } + void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName, + long requiredStorageBytes, @Nullable PendingIntent userActionIntent, + IntentSender unarchiveIntentSender, int userId) { + final Intent broadcastIntent = new Intent(); + broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName); + broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status); + + if (status != UNARCHIVAL_OK) { + final Intent dialogIntent = createErrorDialogIntent(status, installerPackageName, + appPackageName, + requiredStorageBytes, userActionIntent, userId); + if (dialogIntent == null) { + // Error already logged. + return; + } + broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent); + } + + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_DENIED); + try { + unarchiveIntentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null, + /* handler= */ null, /* requiredPermission= */ null, + options.toBundle()); + } catch (IntentSender.SendIntentException e) { + Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e); + } + } + + @Nullable + private Intent createErrorDialogIntent(int status, String installerPackageName, + String appPackageName, + long requiredStorageBytes, PendingIntent userActionIntent, int userId) { + final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_ERROR_DIALOG); + dialogIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status); + if (requiredStorageBytes > 0) { + dialogIntent.putExtra(EXTRA_REQUIRED_BYTES, requiredStorageBytes); + } + // Note that the userActionIntent is provided by the installer and is used only by the + // system package installer as a follow-up action after the user confirms the dialog. + if (userActionIntent != null) { + dialogIntent.putExtra(Intent.EXTRA_INTENT, userActionIntent); + } + dialogIntent.putExtra(EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName); + // We fetch this label from the archive state because the installer might not be installed + // anymore in an edge case. + String installerTitle = getInstallerTitle(appPackageName, userId); + if (installerTitle == null) { + // Error already logged. + return null; + } + dialogIntent.putExtra(EXTRA_INSTALLER_TITLE, installerTitle); + return dialogIntent; + } + + private String getInstallerTitle(String appPackageName, int userId) { + PackageStateInternal packageState; + try { + packageState = getPackageState(appPackageName, + mPm.snapshotComputer(), + Process.SYSTEM_UID, userId); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, TextUtils.formatSimple( + "notifyUnarchivalListener: Couldn't fetch package state for %s.", + appPackageName), e); + return null; + } + ArchiveState archiveState = packageState.getUserStateOrDefault(userId).getArchiveState(); + if (archiveState == null) { + Slog.e(TAG, TextUtils.formatSimple("notifyUnarchivalListener: App not archived %s.", + appPackageName)); + return null; + } + return archiveState.getInstallerTitle(); + } + @NonNull private static PackageStateInternal getPackageState(String packageName, Computer snapshot, int callingUid, int userId) diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 882e05dd778b..f731f95b404d 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -16,9 +16,10 @@ package com.android.server.pm; -import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; +import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED; +import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED; import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE; import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY; import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED; @@ -1703,7 +1704,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements }); } - // TODO(b/307299702) Implement error dialog and propagate userActionIntent. @Override public void reportUnarchivalStatus( int unarchiveId, @@ -1746,8 +1746,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements // Execute expensive calls outside the sync block. mPm.mHandler.post( - () -> notifyUnarchivalListener(status, session.params.appPackageName, - unarchiveIntentSender)); + () -> mPackageArchiver.notifyUnarchivalListener(status, + session.getInstallerPackageName(), + session.params.appPackageName, requiredStorageBytes, userActionIntent, + unarchiveIntentSender, userId)); session.params.unarchiveIntentSender = null; if (status != UNARCHIVAL_OK) { Binder.withCleanCallingIdentity(session::abandon); @@ -1776,29 +1778,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements UNARCHIVAL_ERROR_USER_ACTION_NEEDED, UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE, UNARCHIVAL_ERROR_NO_CONNECTIVITY, + UNARCHIVAL_ERROR_INSTALLER_DISABLED, + UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED, UNARCHIVAL_GENERIC_ERROR).contains(status)) { throw new IllegalStateException("Invalid status code passed " + status); } } - private void notifyUnarchivalListener(int status, String packageName, - IntentSender unarchiveIntentSender) { - final Intent fillIn = new Intent(); - fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); - fillIn.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, status); - // TODO(b/307299702) Attach failure dialog with EXTRA_INTENT and requiredStorageBytes here. - final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setPendingIntentBackgroundActivityStartMode( - MODE_BACKGROUND_ACTIVITY_START_DENIED); - try { - unarchiveIntentSender.sendIntent(mContext, 0, fillIn, /* onFinished= */ null, - /* handler= */ null, /* requiredPermission= */ null, - options.toBundle()); - } catch (SendIntentException e) { - Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e); - } - } - private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, int installerUid) { int count = 0; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index adb6906a2915..5daada94d815 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1534,8 +1534,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService return; } for (int userId : userIds) { - var archiveState = PackageArchiver.createArchiveState(archivePackage, userId, - responsibleInstallerPackage); + var archiveState = mInstallerService.mPackageArchiver.createArchiveState( + archivePackage, userId, responsibleInstallerPackage); if (archiveState == null) { continue; } 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..a3ec936273b4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -45,6 +45,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; @@ -92,6 +93,7 @@ public class PackageArchiverTest { private static final String PACKAGE = "com.example"; private static final String CALLER_PACKAGE = "com.caller"; private static final String INSTALLER_PACKAGE = "com.installer"; + private static final String INSTALLER_LABEL = "Installer"; private static final Path ICON_PATH = Path.of("icon.png"); @Rule @@ -198,6 +200,10 @@ public class PackageArchiverTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn( mock(Resources.class)); + ApplicationInfo installerAi = mock(ApplicationInfo.class); + when(mComputer.getApplicationInfo(eq(INSTALLER_PACKAGE), anyLong(), anyInt())).thenReturn( + installerAi); + when(installerAi.loadLabel(any())).thenReturn(INSTALLER_LABEL); when(mInstallerService.createSessionInternal(any(), any(), any(), anyInt(), anyInt())).thenReturn(1); when(mInstallerService.getExistingDraftSessionId(anyInt(), any(), anyInt())).thenReturn( @@ -545,7 +551,7 @@ public class PackageArchiverTest { ICON_PATH, null); activityInfos.add(activityInfo); } - return new ArchiveState(activityInfos, INSTALLER_PACKAGE); + return new ArchiveState(activityInfos, INSTALLER_LABEL); } private static List<LauncherActivityInfo> createLauncherActivities() { |