1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import static android.app.ActivityManager.START_ABORTED; 20 import static android.app.ActivityManager.START_CLASS_NOT_FOUND; 21 import static android.app.ActivityManager.START_PERMISSION_DENIED; 22 import static android.app.AppOpsManager.MODE_ALLOWED; 23 import static android.app.AppOpsManager.MODE_IGNORED; 24 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; 25 import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap; 26 import static android.content.pm.ArchivedActivityInfo.drawableToBitmap; 27 import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS; 28 import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION; 29 import static android.content.pm.PackageInstaller.UNARCHIVAL_OK; 30 import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET; 31 import static android.content.pm.PackageManager.DELETE_ALL_USERS; 32 import static android.content.pm.PackageManager.DELETE_ARCHIVE; 33 import static android.content.pm.PackageManager.DELETE_KEEP_DATA; 34 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE; 35 import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; 36 import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction; 37 import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE; 38 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; 39 40 import android.Manifest; 41 import android.annotation.NonNull; 42 import android.annotation.Nullable; 43 import android.annotation.RequiresPermission; 44 import android.annotation.UserIdInt; 45 import android.app.ActivityManager; 46 import android.app.AppOpsManager; 47 import android.app.BroadcastOptions; 48 import android.app.PendingIntent; 49 import android.content.ComponentName; 50 import android.content.Context; 51 import android.content.IIntentReceiver; 52 import android.content.IIntentSender; 53 import android.content.Intent; 54 import android.content.IntentSender; 55 import android.content.pm.ApplicationInfo; 56 import android.content.pm.ArchivedActivityParcel; 57 import android.content.pm.ArchivedPackageInfo; 58 import android.content.pm.ArchivedPackageParcel; 59 import android.content.pm.Flags; 60 import android.content.pm.LauncherActivityInfo; 61 import android.content.pm.LauncherApps; 62 import android.content.pm.PackageInstaller; 63 import android.content.pm.PackageManager; 64 import android.content.pm.ParceledListSlice; 65 import android.content.pm.ResolveInfo; 66 import android.content.pm.UserInfo; 67 import android.content.pm.VersionedPackage; 68 import android.graphics.Bitmap; 69 import android.graphics.BitmapFactory; 70 import android.graphics.Color; 71 import android.graphics.PorterDuff; 72 import android.graphics.PorterDuffColorFilter; 73 import android.graphics.drawable.AdaptiveIconDrawable; 74 import android.graphics.drawable.BitmapDrawable; 75 import android.graphics.drawable.ColorDrawable; 76 import android.graphics.drawable.Drawable; 77 import android.graphics.drawable.InsetDrawable; 78 import android.graphics.drawable.LayerDrawable; 79 import android.os.Binder; 80 import android.os.Bundle; 81 import android.os.Environment; 82 import android.os.FileUtils; 83 import android.os.IBinder; 84 import android.os.ParcelableException; 85 import android.os.Process; 86 import android.os.RemoteException; 87 import android.os.SELinux; 88 import android.os.UserHandle; 89 import android.os.UserManager; 90 import android.text.TextUtils; 91 import android.util.ExceptionUtils; 92 import android.util.Pair; 93 import android.util.Slog; 94 import android.util.SparseArray; 95 96 import com.android.internal.R; 97 import com.android.internal.annotations.GuardedBy; 98 import com.android.internal.annotations.VisibleForTesting; 99 import com.android.server.pm.pkg.ArchiveState; 100 import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo; 101 import com.android.server.pm.pkg.PackageState; 102 import com.android.server.pm.pkg.PackageStateInternal; 103 import com.android.server.pm.pkg.PackageUserState; 104 import com.android.server.pm.pkg.PackageUserStateInternal; 105 106 import java.io.File; 107 import java.io.FileOutputStream; 108 import java.io.IOException; 109 import java.nio.file.Path; 110 import java.util.ArrayList; 111 import java.util.HashMap; 112 import java.util.List; 113 import java.util.Map; 114 import java.util.Objects; 115 import java.util.Set; 116 import java.util.concurrent.CompletableFuture; 117 118 /** 119 * Responsible archiving apps and returning information about archived apps. 120 * 121 * <p> An archived app is in a state where the app is not fully on the device. APKs are removed 122 * while the data directory is kept. Archived apps are included in the list of launcher apps where 123 * tapping them re-installs the full app. 124 */ 125 public class PackageArchiver { 126 127 private static final String TAG = "PackageArchiverService"; 128 private static final boolean DEBUG = true; 129 130 public static final String EXTRA_UNARCHIVE_INTENT_SENDER = 131 "android.content.pm.extra.UNARCHIVE_INTENT_SENDER"; 132 133 /** 134 * The maximum time granted for an app store to start a foreground service when unarchival 135 * is requested. 136 */ 137 // TODO(b/297358628) Make this configurable through a flag. 138 private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000; 139 140 private static final String ARCHIVE_ICONS_DIR = "package_archiver"; 141 142 private static final String ACTION_UNARCHIVE_DIALOG = 143 "com.android.intent.action.UNARCHIVE_DIALOG"; 144 private static final String ACTION_UNARCHIVE_ERROR_DIALOG = 145 "com.android.intent.action.UNARCHIVE_ERROR_DIALOG"; 146 147 private static final String EXTRA_REQUIRED_BYTES = 148 "com.android.content.pm.extra.UNARCHIVE_EXTRA_REQUIRED_BYTES"; 149 private static final String EXTRA_INSTALLER_PACKAGE_NAME = 150 "com.android.content.pm.extra.UNARCHIVE_INSTALLER_PACKAGE_NAME"; 151 private static final String EXTRA_INSTALLER_TITLE = 152 "com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE"; 153 154 private static final PorterDuffColorFilter OPACITY_LAYER_FILTER = 155 new PorterDuffColorFilter( 156 Color.argb(0.5f /* alpha */, 0f /* red */, 0f /* green */, 0f /* blue */), 157 PorterDuff.Mode.SRC_ATOP); 158 159 private final Context mContext; 160 private final PackageManagerService mPm; 161 162 private final AppStateHelper mAppStateHelper; 163 164 @Nullable 165 private LauncherApps mLauncherApps; 166 167 @Nullable 168 private AppOpsManager mAppOpsManager; 169 170 @Nullable 171 private UserManager mUserManager; 172 173 /* IntentSender store that maps key: {userId, appPackageName} to respective existing attached 174 unarchival intent sender. */ 175 private final Map<Pair<Integer, String>, IntentSender> mLauncherIntentSenders; 176 PackageArchiver(Context context, PackageManagerService mPm)177 PackageArchiver(Context context, PackageManagerService mPm) { 178 this.mContext = context; 179 this.mPm = mPm; 180 this.mAppStateHelper = new AppStateHelper(mContext); 181 this.mLauncherIntentSenders = new HashMap<>(); 182 } 183 184 /** Returns whether a package is archived for a user. */ isArchived(PackageUserState userState)185 public static boolean isArchived(PackageUserState userState) { 186 return userState.getArchiveState() != null && !userState.isInstalled(); 187 } 188 isArchivingEnabled()189 public static boolean isArchivingEnabled() { 190 return Flags.archiving(); 191 } 192 193 @VisibleForTesting requestArchive( @onNull String packageName, @NonNull String callerPackageName, @NonNull IntentSender intentSender, @NonNull UserHandle userHandle)194 void requestArchive( 195 @NonNull String packageName, 196 @NonNull String callerPackageName, 197 @NonNull IntentSender intentSender, 198 @NonNull UserHandle userHandle) { 199 requestArchive(packageName, callerPackageName, /*flags=*/ 0, intentSender, userHandle); 200 } 201 requestArchive( @onNull String packageName, @NonNull String callerPackageName, int flags, @NonNull IntentSender intentSender, @NonNull UserHandle userHandle)202 void requestArchive( 203 @NonNull String packageName, 204 @NonNull String callerPackageName, 205 int flags, 206 @NonNull IntentSender intentSender, 207 @NonNull UserHandle userHandle) { 208 Objects.requireNonNull(packageName); 209 Objects.requireNonNull(callerPackageName); 210 Objects.requireNonNull(intentSender); 211 Objects.requireNonNull(userHandle); 212 213 Slog.i(TAG, 214 TextUtils.formatSimple("Requested archival of package %s for user %s.", packageName, 215 userHandle.getIdentifier())); 216 Computer snapshot = mPm.snapshotComputer(); 217 int binderUserId = userHandle.getIdentifier(); 218 int binderUid = Binder.getCallingUid(); 219 int binderPid = Binder.getCallingPid(); 220 if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) { 221 verifyCaller(snapshot.getPackageUid(callerPackageName, 0, binderUserId), binderUid); 222 } 223 224 final boolean deleteAllUsers = (flags & PackageManager.DELETE_ALL_USERS) != 0; 225 final int[] users = deleteAllUsers ? mPm.mInjector.getUserManagerInternal().getUserIds() 226 : new int[]{binderUserId}; 227 for (int userId : users) { 228 snapshot.enforceCrossUserPermission(binderUid, userId, 229 /*requireFullPermission=*/ true, /*checkShell=*/ true, 230 "archiveApp"); 231 } 232 verifyUninstallPermissions(); 233 234 CompletableFuture<Void>[] archiveStateStored = new CompletableFuture[users.length]; 235 try { 236 for (int i = 0, size = users.length; i < size; ++i) { 237 archiveStateStored[i] = createAndStoreArchiveState(packageName, users[i]); 238 } 239 } catch (PackageManager.NameNotFoundException e) { 240 Slog.e(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", 241 packageName, e.getMessage())); 242 throw new ParcelableException(e); 243 } 244 245 final int deleteFlags = DELETE_ARCHIVE | DELETE_KEEP_DATA 246 | (deleteAllUsers ? DELETE_ALL_USERS : 0); 247 248 CompletableFuture.allOf(archiveStateStored).thenAccept(ignored -> 249 mPm.mInstallerService.uninstall( 250 new VersionedPackage(packageName, 251 PackageManager.VERSION_CODE_HIGHEST), 252 callerPackageName, 253 deleteFlags, 254 intentSender, 255 binderUserId, 256 binderUid, 257 binderPid) 258 ).exceptionally( 259 e -> { 260 Slog.e(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", 261 packageName, e.getMessage())); 262 sendFailureStatus(intentSender, packageName, e.getMessage()); 263 return null; 264 } 265 ); 266 } 267 268 /** 269 * Starts unarchival for the package corresponding to the startActivity intent. Note that this 270 * will work only if the caller is the default/Home Launcher or if activity is started via Shell 271 * identity. 272 */ 273 @NonNull requestUnarchiveOnActivityStart(@ullable Intent intent, @Nullable String callerPackageName, int userId, int callingUid)274 public int requestUnarchiveOnActivityStart(@Nullable Intent intent, 275 @Nullable String callerPackageName, int userId, int callingUid) { 276 String packageName = getPackageNameFromIntent(intent); 277 if (packageName == null) { 278 Slog.e(TAG, "packageName cannot be null for unarchival!"); 279 return START_CLASS_NOT_FOUND; 280 } 281 if (callerPackageName == null) { 282 Slog.e(TAG, "callerPackageName cannot be null for unarchival!"); 283 return START_CLASS_NOT_FOUND; 284 } 285 286 String currentLauncherPackageName = getCurrentLauncherPackageName(getParentUserId(userId)); 287 if ((currentLauncherPackageName == null || !TextUtils.equals(callerPackageName, 288 currentLauncherPackageName)) && callingUid != Process.SHELL_UID) { 289 // TODO(b/311619990): Remove dependency on SHELL_UID for testing 290 Slog.e(TAG, TextUtils.formatSimple( 291 "callerPackageName: %s does not qualify for unarchival of package: " + "%s!", 292 callerPackageName, packageName)); 293 return START_PERMISSION_DENIED; 294 } 295 296 try { 297 boolean openAppDetailsIfOngoingUnarchival = getAppOpsManager().checkOp( 298 AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName) 299 == MODE_ALLOWED; 300 if (openAppDetailsIfOngoingUnarchival) { 301 PackageInstaller.SessionInfo activeUnarchivalSession = getActiveUnarchivalSession( 302 packageName, userId); 303 if (activeUnarchivalSession != null) { 304 mPm.mHandler.post(() -> { 305 Slog.i(TAG, "Opening app details page for ongoing unarchival of: " 306 + packageName); 307 getLauncherApps().startPackageInstallerSessionDetailsActivity( 308 activeUnarchivalSession, null, null); 309 }); 310 return START_ABORTED; 311 } 312 } 313 314 Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName)); 315 316 requestUnarchive(packageName, callerPackageName, 317 getOrCreateLauncherListener(userId, packageName), 318 UserHandle.of(userId), 319 getAppOpsManager().checkOp( 320 AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName) 321 == MODE_ALLOWED); 322 } catch (Throwable t) { 323 Slog.e(TAG, TextUtils.formatSimple( 324 "Unexpected error occurred while unarchiving package %s: %s.", packageName, 325 t.getLocalizedMessage())); 326 } 327 328 // We return STATUS_ABORTED because: 329 // 1. Archived App is not actually present during activity start. Hence the unarchival 330 // start should be treated as an error code. 331 // 2. STATUS_ABORTED is not visible to the end consumers. Hence, it will not change user 332 // experience. 333 // 3. Returning STATUS_ABORTED helps us avoid manually handling of different cases like 334 // aborting activity options, animations etc in the Windows Manager. 335 return START_ABORTED; 336 } 337 338 // Profiles share their UI and default apps, so we have to get the profile parent before 339 // fetching the default launcher. getParentUserId(int userId)340 private int getParentUserId(int userId) { 341 UserInfo profileParent = getUserManager().getProfileParent(userId); 342 return profileParent == null ? userId : profileParent.id; 343 } 344 345 /** 346 * Returns true if the componentName targeted by the intent corresponds to that of an archived 347 * app. 348 */ isIntentResolvedToArchivedApp(Intent intent, int userId)349 public boolean isIntentResolvedToArchivedApp(Intent intent, int userId) { 350 String packageName = getPackageNameFromIntent(intent); 351 if (packageName == null || intent.getComponent() == null) { 352 return false; 353 } 354 PackageState packageState = mPm.snapshotComputer().getPackageStateInternal(packageName); 355 if (packageState == null) { 356 return false; 357 } 358 PackageUserState userState = packageState.getUserStateOrDefault(userId); 359 if (!PackageArchiver.isArchived(userState)) { 360 return false; 361 } 362 List<ArchiveState.ArchiveActivityInfo> archiveActivityInfoList = 363 userState.getArchiveState().getActivityInfos(); 364 for (int i = 0; i < archiveActivityInfoList.size(); i++) { 365 if (archiveActivityInfoList.get(i) 366 .getOriginalComponentName().equals(intent.getComponent())) { 367 return true; 368 } 369 } 370 Slog.e(TAG, TextUtils.formatSimple( 371 "Package: %s is archived but component to start main activity" 372 + " cannot be found!", packageName)); 373 return false; 374 } 375 clearArchiveState(String packageName, int userId)376 void clearArchiveState(String packageName, int userId) { 377 final PackageSetting ps; 378 synchronized (mPm.mLock) { 379 ps = mPm.mSettings.getPackageLPr(packageName); 380 } 381 clearArchiveState(ps, userId); 382 } 383 clearArchiveState(PackageSetting ps, int userId)384 void clearArchiveState(PackageSetting ps, int userId) { 385 synchronized (mPm.mLock) { 386 if (ps == null || ps.getUserStateOrDefault(userId).getArchiveState() == null) { 387 // No archive states to clear 388 return; 389 } 390 if (DEBUG) { 391 Slog.e(TAG, "Clearing archive states for " + ps.getPackageName()); 392 } 393 ps.setArchiveState(/* archiveState= */ null, userId); 394 } 395 File iconsDir = getIconsDir(ps.getPackageName(), userId); 396 if (!iconsDir.exists()) { 397 if (DEBUG) { 398 Slog.e(TAG, "Icons are already deleted at " + iconsDir.getAbsolutePath()); 399 } 400 return; 401 } 402 // TODO(b/319238030) Move this into installd. 403 if (!FileUtils.deleteContentsAndDir(iconsDir)) { 404 Slog.e(TAG, "Failed to clean up archive files for " + ps.getPackageName()); 405 } else { 406 if (DEBUG) { 407 Slog.e(TAG, "Deleted icons at " + iconsDir.getAbsolutePath()); 408 } 409 } 410 } 411 412 @Nullable getCurrentLauncherPackageName(int userId)413 private String getCurrentLauncherPackageName(int userId) { 414 ComponentName defaultLauncherComponent = mPm.snapshotComputer().getDefaultHomeActivity( 415 userId); 416 if (defaultLauncherComponent != null) { 417 return defaultLauncherComponent.getPackageName(); 418 } 419 return null; 420 } 421 isCallingPackageValid(String callingPackage, int callingUid, int userId)422 private boolean isCallingPackageValid(String callingPackage, int callingUid, int userId) { 423 int packageUid; 424 packageUid = mPm.snapshotComputer().getPackageUid(callingPackage, 0L, userId); 425 if (packageUid != callingUid) { 426 Slog.w(TAG, TextUtils.formatSimple("Calling package: %s does not belong to uid: %d", 427 callingPackage, callingUid)); 428 return false; 429 } 430 return true; 431 } 432 getOrCreateLauncherListener(int userId, String packageName)433 private IntentSender getOrCreateLauncherListener(int userId, String packageName) { 434 Pair<Integer, String> key = Pair.create(userId, packageName); 435 synchronized (mLauncherIntentSenders) { 436 IntentSender intentSender = mLauncherIntentSenders.get(key); 437 if (intentSender != null) { 438 return intentSender; 439 } 440 IntentSender unarchiveIntentSender = new IntentSender( 441 (IIntentSender) new UnarchiveIntentSender()); 442 mLauncherIntentSenders.put(key, unarchiveIntentSender); 443 return unarchiveIntentSender; 444 } 445 } 446 447 /** Creates archived state for the package and user. */ createAndStoreArchiveState(String packageName, int userId)448 private CompletableFuture<Void> createAndStoreArchiveState(String packageName, int userId) 449 throws PackageManager.NameNotFoundException { 450 Computer snapshot = mPm.snapshotComputer(); 451 PackageStateInternal ps = getPackageState(packageName, snapshot, 452 Binder.getCallingUid(), userId); 453 verifyNotSystemApp(ps.getFlags()); 454 verifyInstalled(ps, userId); 455 String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); 456 ApplicationInfo installerInfo = verifyInstaller( 457 snapshot, responsibleInstallerPackage, userId); 458 verifyOptOutStatus(packageName, 459 UserHandle.getUid(userId, UserHandle.getUid(userId, ps.getAppId()))); 460 461 List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(), 462 userId); 463 final CompletableFuture<Void> archiveStateStored = new CompletableFuture<>(); 464 mPm.mHandler.post(() -> { 465 try { 466 final String installerTitle = getResponsibleInstallerTitle( 467 mContext, installerInfo, responsibleInstallerPackage, userId); 468 var archiveState = createArchiveStateInternal(packageName, userId, mainActivities, 469 installerTitle); 470 storeArchiveState(packageName, archiveState, userId); 471 archiveStateStored.complete(null); 472 } catch (IOException | PackageManager.NameNotFoundException e) { 473 archiveStateStored.completeExceptionally(e); 474 } 475 }); 476 return archiveStateStored; 477 } 478 479 @Nullable createArchiveState(@onNull ArchivedPackageParcel archivedPackage, int userId, String installerPackage, String responsibleInstallerTitle)480 ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage, 481 int userId, String installerPackage, String responsibleInstallerTitle) { 482 ApplicationInfo installerInfo = mPm.snapshotComputer().getApplicationInfo( 483 installerPackage, /* flags= */ 0, userId); 484 if (installerInfo == null) { 485 // Should never happen because we just fetched the installerInfo. 486 Slog.e(TAG, "Couldn't find installer " + installerPackage); 487 return null; 488 } 489 if (responsibleInstallerTitle == null) { 490 Slog.e(TAG, "Couldn't get the title of the installer"); 491 return null; 492 } 493 494 final int iconSize = mContext.getSystemService( 495 ActivityManager.class).getLauncherLargeIconSize(); 496 497 var info = new ArchivedPackageInfo(archivedPackage); 498 499 try { 500 var packageName = info.getPackageName(); 501 var mainActivities = info.getLauncherActivities(); 502 List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); 503 for (int i = 0, size = mainActivities.size(); i < size; ++i) { 504 var mainActivity = mainActivities.get(i); 505 Path iconPath = storeAdaptiveDrawable( 506 packageName, mainActivity.getIcon(), userId, i * 2 + 0, iconSize); 507 Path monochromePath = storeAdaptiveDrawable( 508 packageName, mainActivity.getMonochromeIcon(), userId, i * 2 + 1, iconSize); 509 ArchiveActivityInfo activityInfo = 510 new ArchiveActivityInfo( 511 mainActivity.getLabel().toString(), 512 mainActivity.getComponentName(), 513 iconPath, 514 monochromePath); 515 archiveActivityInfos.add(activityInfo); 516 } 517 518 return new ArchiveState(archiveActivityInfos, responsibleInstallerTitle); 519 } catch (IOException e) { 520 Slog.e(TAG, "Failed to create archive state", e); 521 return null; 522 } 523 } 524 createArchiveStateInternal(String packageName, int userId, List<LauncherActivityInfo> mainActivities, String installerTitle)525 ArchiveState createArchiveStateInternal(String packageName, int userId, 526 List<LauncherActivityInfo> mainActivities, String installerTitle) 527 throws IOException { 528 final int iconSize = mContext.getSystemService( 529 ActivityManager.class).getLauncherLargeIconSize(); 530 531 List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); 532 for (int i = 0, size = mainActivities.size(); i < size; i++) { 533 LauncherActivityInfo mainActivity = mainActivities.get(i); 534 Path iconPath = storeIcon(packageName, mainActivity, userId, i * 2 + 0, iconSize); 535 // i * 2 + 1 reserved for monochromeIcon 536 ArchiveActivityInfo activityInfo = 537 new ArchiveActivityInfo( 538 mainActivity.getLabel().toString(), 539 mainActivity.getComponentName(), 540 iconPath, 541 null); 542 archiveActivityInfos.add(activityInfo); 543 } 544 545 return new ArchiveState(archiveActivityInfos, installerTitle); 546 } 547 548 @VisibleForTesting storeIcon(String packageName, LauncherActivityInfo mainActivity, @UserIdInt int userId, int index, int iconSize)549 Path storeIcon(String packageName, LauncherActivityInfo mainActivity, 550 @UserIdInt int userId, int index, int iconSize) throws IOException { 551 int iconResourceId = mainActivity.getActivityInfo().getIconResource(); 552 if (iconResourceId == 0) { 553 // The app doesn't define an icon. No need to store anything. 554 return null; 555 } 556 return storeDrawable(packageName, mainActivity.getIcon(/* density= */ 0), userId, index, 557 iconSize); 558 } 559 storeDrawable(String packageName, @Nullable Drawable iconDrawable, @UserIdInt int userId, int index, int iconSize)560 private static Path storeDrawable(String packageName, @Nullable Drawable iconDrawable, 561 @UserIdInt int userId, int index, int iconSize) throws IOException { 562 if (iconDrawable == null) { 563 return null; 564 } 565 File iconsDir = createIconsDir(packageName, userId); 566 File iconFile = new File(iconsDir, index + ".png"); 567 Bitmap icon = drawableToBitmap(iconDrawable, iconSize); 568 try (FileOutputStream out = new FileOutputStream(iconFile)) { 569 // Note: Quality is ignored for PNGs. 570 if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) { 571 throw new IOException(TextUtils.formatSimple("Failure to store icon file %s", 572 iconFile.getAbsolutePath())); 573 } 574 out.flush(); 575 } 576 if (DEBUG && iconFile.exists()) { 577 Slog.i(TAG, "Stored icon at " + iconFile.getAbsolutePath()); 578 } 579 return iconFile.toPath(); 580 } 581 582 /** 583 * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size. 584 * This allows the badging to be done based on the actual bitmap size rather than 585 * the scaled bitmap size. 586 */ 587 private static class FixedSizeBitmapDrawable extends BitmapDrawable { 588 FixedSizeBitmapDrawable(@ullable final Bitmap bitmap)589 FixedSizeBitmapDrawable(@Nullable final Bitmap bitmap) { 590 super(null, bitmap); 591 } 592 593 @Override getIntrinsicHeight()594 public int getIntrinsicHeight() { 595 return getBitmap().getWidth(); 596 } 597 598 @Override getIntrinsicWidth()599 public int getIntrinsicWidth() { 600 return getBitmap().getWidth(); 601 } 602 } 603 604 /** 605 * Create an <a 606 * href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive"> 607 * adaptive icon</a> from an icon. 608 * This is necessary so the icon can be displayed properly by different launchers. 609 */ storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable, @UserIdInt int userId, int index, int iconSize)610 private static Path storeAdaptiveDrawable(String packageName, @Nullable Drawable iconDrawable, 611 @UserIdInt int userId, int index, int iconSize) throws IOException { 612 if (iconDrawable == null) { 613 return null; 614 } 615 616 // see BaseIconFactory#createShapedIconBitmap 617 if (iconDrawable instanceof BitmapDrawable) { 618 var icon = ((BitmapDrawable) iconDrawable).getBitmap(); 619 iconDrawable = new FixedSizeBitmapDrawable(icon); 620 } 621 622 float inset = getExtraInsetFraction(); 623 inset = inset / (1 + 2 * inset); 624 Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), 625 new InsetDrawable(iconDrawable/*d*/, inset, inset, inset, inset)); 626 627 return storeDrawable(packageName, d, userId, index, iconSize); 628 } 629 630 verifyInstaller(Computer snapshot, String installerPackageName, int userId)631 private ApplicationInfo verifyInstaller(Computer snapshot, String installerPackageName, 632 int userId) throws PackageManager.NameNotFoundException { 633 if (TextUtils.isEmpty(installerPackageName)) { 634 throw new PackageManager.NameNotFoundException("No installer found"); 635 } 636 // Allow shell for easier development. 637 if ((Binder.getCallingUid() != Process.SHELL_UID) 638 && !verifySupportsUnarchival(installerPackageName, userId)) { 639 throw new PackageManager.NameNotFoundException("Installer does not support unarchival"); 640 } 641 ApplicationInfo appInfo = snapshot.getApplicationInfo( 642 installerPackageName, /* flags=*/ 0, userId); 643 if (appInfo == null) { 644 throw new PackageManager.NameNotFoundException("Failed to obtain Installer info"); 645 } 646 return appInfo; 647 } 648 649 /** 650 * Returns true if {@code installerPackage} supports unarchival being able to handle 651 * {@link Intent#ACTION_UNARCHIVE_PACKAGE} 652 */ verifySupportsUnarchival(String installerPackage, int userId)653 public boolean verifySupportsUnarchival(String installerPackage, int userId) { 654 if (TextUtils.isEmpty(installerPackage)) { 655 return false; 656 } 657 658 Intent intent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE).setPackage(installerPackage); 659 660 ParceledListSlice<ResolveInfo> intentReceivers = 661 Binder.withCleanCallingIdentity( 662 () -> mPm.queryIntentReceivers(mPm.snapshotComputer(), 663 intent, /* resolvedType= */ null, /* flags= */ 0, userId)); 664 return intentReceivers != null && !intentReceivers.getList().isEmpty(); 665 } 666 verifyNotSystemApp(int flags)667 private void verifyNotSystemApp(int flags) throws PackageManager.NameNotFoundException { 668 if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || ( 669 (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { 670 throw new PackageManager.NameNotFoundException("System apps cannot be archived."); 671 } 672 } 673 verifyInstalled(PackageStateInternal ps, int userId)674 private void verifyInstalled(PackageStateInternal ps, int userId) 675 throws PackageManager.NameNotFoundException { 676 if (!ps.getUserStateOrDefault(userId).isInstalled()) { 677 throw new PackageManager.NameNotFoundException( 678 TextUtils.formatSimple("%s is not installed.", ps.getPackageName())); 679 } 680 } 681 682 /** 683 * Returns true if the app is archivable. 684 */ isAppArchivable(@onNull String packageName, @NonNull UserHandle user)685 public boolean isAppArchivable(@NonNull String packageName, @NonNull UserHandle user) { 686 Objects.requireNonNull(packageName); 687 Objects.requireNonNull(user); 688 689 Computer snapshot = mPm.snapshotComputer(); 690 int userId = user.getIdentifier(); 691 int binderUid = Binder.getCallingUid(); 692 snapshot.enforceCrossUserPermission(binderUid, userId, true, true, 693 "isAppArchivable"); 694 PackageStateInternal ps; 695 try { 696 ps = getPackageState(packageName, mPm.snapshotComputer(), 697 Binder.getCallingUid(), userId); 698 } catch (PackageManager.NameNotFoundException e) { 699 throw new ParcelableException(e); 700 } 701 702 if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0 || ( 703 (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) { 704 return false; 705 } 706 707 if (isAppOptedOutOfArchiving(packageName, 708 UserHandle.getUid(userId, ps.getAppId()))) { 709 return false; 710 } 711 712 try { 713 verifyInstaller(snapshot, getResponsibleInstallerPackage(ps), userId); 714 getLauncherActivityInfos(packageName, userId); 715 } catch (PackageManager.NameNotFoundException e) { 716 return false; 717 } 718 719 return true; 720 } 721 722 /** 723 * Returns true if user has opted the app out of archiving through system settings. 724 */ isAppOptedOutOfArchiving(String packageName, int uid)725 private boolean isAppOptedOutOfArchiving(String packageName, int uid) { 726 return Binder.withCleanCallingIdentity(() -> 727 getAppOpsManager().checkOpNoThrow( 728 AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, packageName) 729 == MODE_IGNORED); 730 } 731 verifyOptOutStatus(String packageName, int uid)732 private void verifyOptOutStatus(String packageName, int uid) 733 throws PackageManager.NameNotFoundException { 734 if (isAppOptedOutOfArchiving(packageName, uid)) { 735 throw new PackageManager.NameNotFoundException( 736 TextUtils.formatSimple("The app %s is opted out of archiving.", packageName)); 737 } 738 } 739 requestUnarchive( @onNull String packageName, @NonNull String callerPackageName, @NonNull IntentSender statusReceiver, @NonNull UserHandle userHandle)740 void requestUnarchive( 741 @NonNull String packageName, 742 @NonNull String callerPackageName, 743 @NonNull IntentSender statusReceiver, 744 @NonNull UserHandle userHandle) { 745 requestUnarchive(packageName, callerPackageName, statusReceiver, userHandle, 746 false /* showUnarchivalConfirmation= */); 747 } 748 requestUnarchive( @onNull String packageName, @NonNull String callerPackageName, @NonNull IntentSender statusReceiver, @NonNull UserHandle userHandle, boolean showUnarchivalConfirmation)749 private void requestUnarchive( 750 @NonNull String packageName, 751 @NonNull String callerPackageName, 752 @NonNull IntentSender statusReceiver, 753 @NonNull UserHandle userHandle, boolean showUnarchivalConfirmation) { 754 Objects.requireNonNull(packageName); 755 Objects.requireNonNull(callerPackageName); 756 Objects.requireNonNull(statusReceiver); 757 Objects.requireNonNull(userHandle); 758 759 Computer snapshot = mPm.snapshotComputer(); 760 int userId = userHandle.getIdentifier(); 761 int binderUid = Binder.getCallingUid(); 762 if (!PackageManagerServiceUtils.isSystemOrRootOrShell(binderUid)) { 763 verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid); 764 } 765 snapshot.enforceCrossUserPermission(binderUid, userId, true, true, 766 "unarchiveApp"); 767 768 PackageStateInternal ps; 769 PackageStateInternal callerPs; 770 try { 771 ps = getPackageState(packageName, snapshot, binderUid, userId); 772 callerPs = getPackageState(callerPackageName, snapshot, binderUid, userId); 773 verifyArchived(ps, userId); 774 } catch (PackageManager.NameNotFoundException e) { 775 throw new ParcelableException(e); 776 } 777 String installerPackage = getResponsibleInstallerPackage(ps); 778 if (installerPackage == null) { 779 throw new ParcelableException( 780 new PackageManager.NameNotFoundException( 781 TextUtils.formatSimple("No installer found to unarchive app %s.", 782 packageName))); 783 } 784 785 boolean hasInstallPackages = mContext.checkCallingOrSelfPermission( 786 Manifest.permission.INSTALL_PACKAGES) 787 == PackageManager.PERMISSION_GRANTED; 788 // We don't check the AppOpsManager here for REQUEST_INSTALL_PACKAGES because the requester 789 // is not the source of the installation. 790 boolean hasRequestInstallPackages = callerPs.getAndroidPackage().getRequestedPermissions() 791 .contains(android.Manifest.permission.REQUEST_INSTALL_PACKAGES); 792 if (!hasInstallPackages && !hasRequestInstallPackages) { 793 throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES " 794 + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request " 795 + "an unarchival."); 796 } 797 798 if (!hasInstallPackages || showUnarchivalConfirmation) { 799 requestUnarchiveConfirmation(packageName, statusReceiver, userHandle); 800 return; 801 } 802 803 // TODO(b/311709794) Check that the responsible installer has INSTALL_PACKAGES or 804 // OPSTR_REQUEST_INSTALL_PACKAGES too. Edge case: In reality this should always be the case, 805 // unless a user has disabled the permission after archiving an app. 806 807 int draftSessionId; 808 try { 809 draftSessionId = Binder.withCleanCallingIdentity( 810 () -> createDraftSession(packageName, installerPackage, callerPackageName, 811 statusReceiver, userId)); 812 } catch (RuntimeException e) { 813 if (e.getCause() instanceof IOException) { 814 throw ExceptionUtils.wrap((IOException) e.getCause()); 815 } else { 816 throw e; 817 } 818 } 819 820 mPm.mHandler.post(() -> { 821 Slog.i(TAG, "Starting app unarchival for: " + packageName); 822 unarchiveInternal(packageName, userHandle, installerPackage, 823 draftSessionId); 824 }); 825 } 826 827 @Nullable getActiveUnarchivalSession(String packageName, int userId)828 private PackageInstaller.SessionInfo getActiveUnarchivalSession(String packageName, 829 int userId) { 830 List<PackageInstaller.SessionInfo> activeSessions = 831 mPm.mInstallerService.getAllSessions(userId).getList(); 832 for (int idx = 0; idx < activeSessions.size(); idx++) { 833 PackageInstaller.SessionInfo activeSession = activeSessions.get(idx); 834 if (TextUtils.equals(activeSession.appPackageName, packageName) 835 && activeSession.userId == userId && activeSession.active 836 && activeSession.isUnarchival()) { 837 return activeSession; 838 } 839 } 840 return null; 841 } 842 requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver, UserHandle user)843 private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver, 844 UserHandle user) { 845 final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_DIALOG); 846 dialogIntent.putExtra(EXTRA_UNARCHIVE_INTENT_SENDER, statusReceiver); 847 dialogIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); 848 849 final Intent broadcastIntent = new Intent(); 850 broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); 851 broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, 852 PackageInstaller.STATUS_PENDING_USER_ACTION); 853 broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent); 854 broadcastIntent.putExtra(Intent.EXTRA_USER, user); 855 sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent); 856 } 857 verifyUninstallPermissions()858 private void verifyUninstallPermissions() { 859 if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES) 860 != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission( 861 Manifest.permission.REQUEST_DELETE_PACKAGES) 862 != PackageManager.PERMISSION_GRANTED) { 863 throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES " 864 + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request " 865 + "an archival."); 866 } 867 } 868 createDraftSession(String packageName, String installerPackage, String callerPackageName, IntentSender statusReceiver, int userId)869 private int createDraftSession(String packageName, String installerPackage, 870 String callerPackageName, 871 IntentSender statusReceiver, int userId) throws IOException { 872 PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams( 873 PackageInstaller.SessionParams.MODE_FULL_INSTALL); 874 sessionParams.setAppPackageName(packageName); 875 sessionParams.setAppLabel( 876 mContext.getString(com.android.internal.R.string.unarchival_session_app_label)); 877 sessionParams.setAppIcon( 878 getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName)); 879 // To make sure SessionInfo::isUnarchival returns true for draft sessions, 880 // INSTALL_UNARCHIVE is also set. 881 sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE); 882 883 int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId); 884 // Handles case of repeated unarchival calls for the same package. 885 int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid, 886 sessionParams, 887 userId); 888 if (existingSessionId != PackageInstaller.SessionInfo.INVALID_ID) { 889 attachListenerToSession(statusReceiver, existingSessionId, userId); 890 return existingSessionId; 891 } 892 893 int sessionId = mPm.mInstallerService.createSessionInternal( 894 sessionParams, 895 installerPackage, mContext.getAttributionTag(), 896 installerUid, 897 userId); 898 attachListenerToSession(statusReceiver, sessionId, userId); 899 900 // TODO(b/297358628) Also cleanup sessions upon device restart. 901 mPm.mHandler.postDelayed(() -> mPm.mInstallerService.cleanupDraftIfUnclaimed(sessionId), 902 getUnarchiveForegroundTimeout()); 903 return sessionId; 904 } 905 attachListenerToSession(IntentSender statusReceiver, int existingSessionId, int userId)906 private void attachListenerToSession(IntentSender statusReceiver, int existingSessionId, 907 int userId) { 908 PackageInstallerSession session = mPm.mInstallerService.getSession(existingSessionId); 909 int status = session.getUnarchivalStatus(); 910 // Here we handle a race condition that might happen when an installer reports UNARCHIVAL_OK 911 // but hasn't created a session yet. Without this the listener would never receive a success 912 // response. 913 if (status == UNARCHIVAL_OK) { 914 notifyUnarchivalListener(UNARCHIVAL_OK, session.getInstallerPackageName(), 915 session.params.appPackageName, /* requiredStorageBytes= */ 0, 916 /* userActionIntent= */ null, Set.of(statusReceiver), userId); 917 return; 918 } else if (status != UNARCHIVAL_STATUS_UNSET) { 919 throw new IllegalStateException(TextUtils.formatSimple("Session %s has unarchive status" 920 + "%s but is still active.", session.sessionId, status)); 921 } 922 923 session.registerUnarchivalListener(statusReceiver); 924 } 925 926 /** 927 * Returns the icon of an archived app. This is the icon of the main activity of the app. 928 * 929 * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple 930 * launcher activities, only one of the icons is returned arbitrarily. 931 */ 932 @Nullable getArchivedAppIcon(@onNull String packageName, @NonNull UserHandle user, String callingPackageName)933 public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user, 934 String callingPackageName) { 935 Objects.requireNonNull(packageName); 936 Objects.requireNonNull(user); 937 938 Computer snapshot = mPm.snapshotComputer(); 939 int callingUid = Binder.getCallingUid(); 940 int userId = user.getIdentifier(); 941 PackageStateInternal ps; 942 try { 943 ps = getPackageState(packageName, snapshot, callingUid, userId); 944 } catch (PackageManager.NameNotFoundException e) { 945 Slog.e(TAG, TextUtils.formatSimple("Package %s couldn't be found.", packageName), e); 946 return null; 947 } 948 949 ArchiveState archiveState = getAnyArchiveState(ps, userId); 950 if (archiveState == null || archiveState.getActivityInfos().size() == 0) { 951 return null; 952 } 953 954 // TODO(b/298452477) Handle monochrome icons. 955 // In the rare case the archived app defined more than two launcher activities, we choose 956 // the first one arbitrarily. 957 Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0)); 958 if (icon != null && getAppOpsManager().checkOp( 959 AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName) 960 == MODE_ALLOWED) { 961 icon = includeCloudOverlay(icon); 962 } 963 return icon; 964 } 965 966 /** 967 * This method first checks the ArchiveState for the provided userId and then tries to fallback 968 * to other users if the current user is not archived. 969 * 970 * <p> This fallback behaviour is required for archived apps to fit into the multi-user world 971 * where APKs are shared across users. E.g. current ways of fetching icons for apps that are 972 * only installed on the work profile also work when executed on the personal profile if you're 973 * using {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}. Resource fetching from APKs is for 974 * the most part userId-agnostic, which we need to mimic here in order for existing methods 975 * like {@link PackageManager#getApplicationIcon} to continue working. 976 * 977 * @return {@link ArchiveState} for {@code userId} if present. If not present, false back to an 978 * arbitrary userId. If no user is archived, returns null. 979 */ 980 @Nullable getAnyArchiveState(PackageStateInternal ps, int userId)981 private ArchiveState getAnyArchiveState(PackageStateInternal ps, int userId) { 982 PackageUserStateInternal userState = ps.getUserStateOrDefault(userId); 983 if (isArchived(userState)) { 984 return userState.getArchiveState(); 985 } 986 987 for (int i = 0; i < ps.getUserStates().size(); i++) { 988 userState = ps.getUserStates().valueAt(i); 989 if (isArchived(userState)) { 990 return userState.getArchiveState(); 991 } 992 } 993 994 return null; 995 } 996 997 @VisibleForTesting 998 @Nullable decodeIcon(ArchiveActivityInfo activityInfo)999 Bitmap decodeIcon(ArchiveActivityInfo activityInfo) { 1000 Path iconBitmap = activityInfo.getIconBitmap(); 1001 if (iconBitmap == null) { 1002 return null; 1003 } 1004 Bitmap bitmap = BitmapFactory.decodeFile(iconBitmap.toString()); 1005 // TODO(b/278553670) We should throw here after some time. Failing graciously now because 1006 // we've just changed the place where we store icons. 1007 if (bitmap == null) { 1008 Slog.e(TAG, "Archived icon cannot be decoded " + iconBitmap.toAbsolutePath()); 1009 return null; 1010 } 1011 return bitmap; 1012 } 1013 1014 @Nullable includeCloudOverlay(Bitmap bitmap)1015 Bitmap includeCloudOverlay(Bitmap bitmap) { 1016 Drawable cloudDrawable = 1017 mContext.getResources() 1018 .getDrawable(R.drawable.archived_app_cloud_overlay, mContext.getTheme()); 1019 if (cloudDrawable == null) { 1020 Slog.e(TAG, "Unable to locate cloud overlay for archived app!"); 1021 return bitmap; 1022 } 1023 BitmapDrawable appIconDrawable = new BitmapDrawable(mContext.getResources(), bitmap); 1024 appIconDrawable.setColorFilter(OPACITY_LAYER_FILTER); 1025 appIconDrawable.setBounds( 1026 0 /* left */, 1027 0 /* top */, 1028 cloudDrawable.getIntrinsicWidth(), 1029 cloudDrawable.getIntrinsicHeight()); 1030 LayerDrawable layerDrawable = 1031 new LayerDrawable(new Drawable[]{appIconDrawable, cloudDrawable}); 1032 final int iconSize = mContext.getSystemService( 1033 ActivityManager.class).getLauncherLargeIconSize(); 1034 Bitmap appIconWithCloudOverlay = drawableToBitmap(layerDrawable, iconSize); 1035 if (bitmap != null) { 1036 bitmap.recycle(); 1037 } 1038 return appIconWithCloudOverlay; 1039 } 1040 verifyArchived(PackageStateInternal ps, int userId)1041 private void verifyArchived(PackageStateInternal ps, int userId) 1042 throws PackageManager.NameNotFoundException { 1043 PackageUserStateInternal userState = ps.getUserStateOrDefault(userId); 1044 if (!isArchived(userState)) { 1045 throw new PackageManager.NameNotFoundException( 1046 TextUtils.formatSimple("Package %s is not currently archived.", 1047 ps.getPackageName())); 1048 } 1049 } 1050 1051 @RequiresPermission( 1052 allOf = { 1053 Manifest.permission.INTERACT_ACROSS_USERS, 1054 android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, 1055 android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, 1056 android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}, 1057 conditional = true) unarchiveInternal(String packageName, UserHandle userHandle, String installerPackage, int unarchiveId)1058 private void unarchiveInternal(String packageName, UserHandle userHandle, 1059 String installerPackage, int unarchiveId) { 1060 int userId = userHandle.getIdentifier(); 1061 Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE); 1062 unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 1063 unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ID, unarchiveId); 1064 unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName); 1065 unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS, 1066 userId == UserHandle.USER_ALL); 1067 unarchiveIntent.setPackage(installerPackage); 1068 1069 // If the unarchival is requested for all users, the current user is used for unarchival. 1070 UserHandle userForUnarchival = userId == UserHandle.USER_ALL 1071 ? UserHandle.of(mPm.mUserManager.getCurrentUserId()) 1072 : userHandle; 1073 mContext.sendOrderedBroadcastAsUser( 1074 unarchiveIntent, 1075 userForUnarchival, 1076 /* receiverPermission = */ null, 1077 AppOpsManager.OP_NONE, 1078 createUnarchiveOptions(), 1079 /* resultReceiver= */ null, 1080 /* scheduler= */ null, 1081 /* initialCode= */ 0, 1082 /* initialData= */ null, 1083 /* initialExtras= */ null); 1084 } 1085 getLauncherActivityInfos(String packageName, int userId)1086 List<LauncherActivityInfo> getLauncherActivityInfos(String packageName, 1087 int userId) throws PackageManager.NameNotFoundException { 1088 List<LauncherActivityInfo> mainActivities = 1089 Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList( 1090 packageName, 1091 new UserHandle(userId))); 1092 if (mainActivities.isEmpty()) { 1093 throw new PackageManager.NameNotFoundException( 1094 TextUtils.formatSimple("The app %s does not have a main activity.", 1095 packageName)); 1096 } 1097 1098 return mainActivities; 1099 } 1100 1101 @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, 1102 android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, 1103 android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) createUnarchiveOptions()1104 private Bundle createUnarchiveOptions() { 1105 BroadcastOptions options = BroadcastOptions.makeBasic(); 1106 options.setTemporaryAppAllowlist(getUnarchiveForegroundTimeout(), 1107 TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, 1108 REASON_PACKAGE_UNARCHIVE, ""); 1109 return options.toBundle(); 1110 } 1111 getUnarchiveForegroundTimeout()1112 private static int getUnarchiveForegroundTimeout() { 1113 return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS; 1114 } 1115 getResponsibleInstallerPackage(InstallSource installSource)1116 private static String getResponsibleInstallerPackage(InstallSource installSource) { 1117 return TextUtils.isEmpty(installSource.mUpdateOwnerPackageName) 1118 ? installSource.mInstallerPackageName 1119 : installSource.mUpdateOwnerPackageName; 1120 } 1121 getResponsibleInstallerTitle(Context context, ApplicationInfo appInfo, String responsibleInstallerPackage, int userId)1122 private static String getResponsibleInstallerTitle(Context context, ApplicationInfo appInfo, 1123 String responsibleInstallerPackage, int userId) 1124 throws PackageManager.NameNotFoundException { 1125 final Context userContext = context.createPackageContextAsUser( 1126 responsibleInstallerPackage, /* flags= */ 0, new UserHandle(userId)); 1127 return appInfo.loadLabel(userContext.getPackageManager()).toString(); 1128 } 1129 getResponsibleInstallerPackage(PackageStateInternal ps)1130 static String getResponsibleInstallerPackage(PackageStateInternal ps) { 1131 return getResponsibleInstallerPackage(ps.getInstallSource()); 1132 } 1133 1134 @Nullable getResponsibleInstallerTitles(Context context, Computer snapshot, InstallSource installSource, int requestUserId, int[] allUserIds)1135 static SparseArray<String> getResponsibleInstallerTitles(Context context, Computer snapshot, 1136 InstallSource installSource, int requestUserId, int[] allUserIds) { 1137 final String responsibleInstallerPackage = getResponsibleInstallerPackage(installSource); 1138 final SparseArray<String> responsibleInstallerTitles = new SparseArray<>(); 1139 try { 1140 if (requestUserId != UserHandle.USER_ALL) { 1141 final ApplicationInfo responsibleInstallerInfo = snapshot.getApplicationInfo( 1142 responsibleInstallerPackage, /* flags= */ 0, requestUserId); 1143 if (responsibleInstallerInfo == null) { 1144 return null; 1145 } 1146 1147 final String title = getResponsibleInstallerTitle(context, 1148 responsibleInstallerInfo, responsibleInstallerPackage, requestUserId); 1149 responsibleInstallerTitles.put(requestUserId, title); 1150 } else { 1151 // Go through all userIds. 1152 for (int i = 0; i < allUserIds.length; i++) { 1153 final int userId = allUserIds[i]; 1154 final ApplicationInfo responsibleInstallerInfo = snapshot.getApplicationInfo( 1155 responsibleInstallerPackage, /* flags= */ 0, userId); 1156 // Can't get the applicationInfo on the user. 1157 // Maybe the installer isn't installed on the user. 1158 if (responsibleInstallerInfo == null) { 1159 continue; 1160 } 1161 1162 final String title = getResponsibleInstallerTitle(context, 1163 responsibleInstallerInfo, responsibleInstallerPackage, userId); 1164 responsibleInstallerTitles.put(userId, title); 1165 } 1166 } 1167 } catch (PackageManager.NameNotFoundException ex) { 1168 return null; 1169 } 1170 return responsibleInstallerTitles; 1171 } 1172 notifyUnarchivalListener(int status, String installerPackageName, String appPackageName, long requiredStorageBytes, @Nullable PendingIntent userActionIntent, Set<IntentSender> unarchiveIntentSenders, int userId)1173 void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName, 1174 long requiredStorageBytes, @Nullable PendingIntent userActionIntent, 1175 Set<IntentSender> unarchiveIntentSenders, int userId) { 1176 final Intent broadcastIntent = new Intent(); 1177 broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName); 1178 broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status); 1179 1180 if (status != UNARCHIVAL_OK) { 1181 final Intent dialogIntent = createErrorDialogIntent(status, installerPackageName, 1182 appPackageName, 1183 requiredStorageBytes, userActionIntent, userId); 1184 if (dialogIntent == null) { 1185 // Error already logged. 1186 return; 1187 } 1188 broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent); 1189 broadcastIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)); 1190 } 1191 1192 final BroadcastOptions options = BroadcastOptions.makeBasic(); 1193 options.setPendingIntentBackgroundActivityStartMode( 1194 MODE_BACKGROUND_ACTIVITY_START_DENIED); 1195 for (IntentSender intentSender : unarchiveIntentSenders) { 1196 try { 1197 intentSender.sendIntent(mContext, 0, broadcastIntent, /* onFinished= */ null, 1198 /* handler= */ null, /* requiredPermission= */ null, options.toBundle()); 1199 } catch (IntentSender.SendIntentException e) { 1200 Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e); 1201 } finally { 1202 synchronized (mLauncherIntentSenders) { 1203 mLauncherIntentSenders.remove(Pair.create(userId, appPackageName)); 1204 } 1205 } 1206 } 1207 } 1208 1209 @Nullable createErrorDialogIntent(int status, String installerPackageName, String appPackageName, long requiredStorageBytes, PendingIntent userActionIntent, int userId)1210 private Intent createErrorDialogIntent(int status, String installerPackageName, 1211 String appPackageName, 1212 long requiredStorageBytes, PendingIntent userActionIntent, int userId) { 1213 final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_ERROR_DIALOG); 1214 dialogIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status); 1215 dialogIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)); 1216 if (requiredStorageBytes > 0) { 1217 dialogIntent.putExtra(EXTRA_REQUIRED_BYTES, requiredStorageBytes); 1218 } 1219 // Note that the userActionIntent is provided by the installer and is used only by the 1220 // system package installer as a follow-up action after the user confirms the dialog. 1221 if (userActionIntent != null) { 1222 dialogIntent.putExtra(Intent.EXTRA_INTENT, userActionIntent); 1223 } 1224 dialogIntent.putExtra(EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName); 1225 // We fetch this label from the archive state because the installer might not be installed 1226 // anymore in an edge case. 1227 String installerTitle = getInstallerTitle(appPackageName, userId); 1228 if (installerTitle == null) { 1229 // Error already logged. 1230 return null; 1231 } 1232 dialogIntent.putExtra(EXTRA_INSTALLER_TITLE, installerTitle); 1233 return dialogIntent; 1234 } 1235 getInstallerTitle(String appPackageName, int userId)1236 private String getInstallerTitle(String appPackageName, int userId) { 1237 PackageStateInternal packageState; 1238 try { 1239 packageState = getPackageState(appPackageName, 1240 mPm.snapshotComputer(), 1241 Process.SYSTEM_UID, userId); 1242 } catch (PackageManager.NameNotFoundException e) { 1243 Slog.e(TAG, TextUtils.formatSimple( 1244 "notifyUnarchivalListener: Couldn't fetch package state for %s.", 1245 appPackageName), e); 1246 return null; 1247 } 1248 ArchiveState archiveState = packageState.getUserStateOrDefault(userId).getArchiveState(); 1249 if (archiveState == null) { 1250 Slog.e(TAG, TextUtils.formatSimple("notifyUnarchivalListener: App not archived %s.", 1251 appPackageName)); 1252 return null; 1253 } 1254 return archiveState.getInstallerTitle(); 1255 } 1256 1257 @NonNull getPackageState(String packageName, Computer snapshot, int callingUid, int userId)1258 private static PackageStateInternal getPackageState(String packageName, 1259 Computer snapshot, int callingUid, int userId) 1260 throws PackageManager.NameNotFoundException { 1261 PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid, 1262 userId); 1263 if (ps == null) { 1264 throw new PackageManager.NameNotFoundException( 1265 TextUtils.formatSimple("Package %s not found.", packageName)); 1266 } 1267 return ps; 1268 } 1269 getLauncherApps()1270 private LauncherApps getLauncherApps() { 1271 if (mLauncherApps == null) { 1272 mLauncherApps = mContext.getSystemService(LauncherApps.class); 1273 } 1274 return mLauncherApps; 1275 } 1276 getAppOpsManager()1277 private AppOpsManager getAppOpsManager() { 1278 if (mAppOpsManager == null) { 1279 mAppOpsManager = mContext.getSystemService(AppOpsManager.class); 1280 } 1281 return mAppOpsManager; 1282 } 1283 getUserManager()1284 private UserManager getUserManager() { 1285 if (mUserManager == null) { 1286 mUserManager = mContext.getSystemService(UserManager.class); 1287 } 1288 return mUserManager; 1289 } 1290 storeArchiveState(String packageName, ArchiveState archiveState, int userId)1291 private void storeArchiveState(String packageName, ArchiveState archiveState, int userId) 1292 throws PackageManager.NameNotFoundException { 1293 synchronized (mPm.mLock) { 1294 PackageSetting packageSetting = getPackageSettingLocked(packageName, userId); 1295 packageSetting 1296 .modifyUserState(userId) 1297 .setArchiveState(archiveState); 1298 } 1299 } 1300 1301 @NonNull 1302 @GuardedBy("mPm.mLock") getPackageSettingLocked(String packageName, int userId)1303 private PackageSetting getPackageSettingLocked(String packageName, int userId) 1304 throws PackageManager.NameNotFoundException { 1305 PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); 1306 // Shouldn't happen, we already verify presence of the package in getPackageState() 1307 if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) { 1308 throw new PackageManager.NameNotFoundException( 1309 TextUtils.formatSimple("Package %s not found.", packageName)); 1310 } 1311 return ps; 1312 } 1313 sendFailureStatus(IntentSender statusReceiver, String packageName, String message)1314 private void sendFailureStatus(IntentSender statusReceiver, String packageName, 1315 String message) { 1316 Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", packageName, 1317 message)); 1318 final Intent intent = new Intent(); 1319 intent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName); 1320 intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); 1321 intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, message); 1322 sendIntent(statusReceiver, packageName, message, intent); 1323 } 1324 sendIntent(IntentSender statusReceiver, String packageName, String message, Intent intent)1325 private void sendIntent(IntentSender statusReceiver, String packageName, String message, 1326 Intent intent) { 1327 try { 1328 final BroadcastOptions options = BroadcastOptions.makeBasic(); 1329 options.setPendingIntentBackgroundActivityStartMode( 1330 MODE_BACKGROUND_ACTIVITY_START_DENIED); 1331 statusReceiver.sendIntent(mContext, 0, intent, /* onFinished= */ null, 1332 /* handler= */ null, /* requiredPermission= */ null, options.toBundle()); 1333 } catch (IntentSender.SendIntentException e) { 1334 Slog.e( 1335 TAG, 1336 TextUtils.formatSimple("Failed to send status for %s with message %s", 1337 packageName, message), 1338 e); 1339 } 1340 } 1341 verifyCaller(int providedUid, int binderUid)1342 private static void verifyCaller(int providedUid, int binderUid) { 1343 if (providedUid != binderUid) { 1344 throw new SecurityException( 1345 TextUtils.formatSimple( 1346 "The UID %s of callerPackageName set by the caller doesn't match the " 1347 + "caller's actual UID %s.", 1348 providedUid, 1349 binderUid)); 1350 } 1351 } 1352 createIconsDir(String packageName, @UserIdInt int userId)1353 private static File createIconsDir(String packageName, @UserIdInt int userId) 1354 throws IOException { 1355 File iconsDir = getIconsDir(packageName, userId); 1356 if (!iconsDir.isDirectory()) { 1357 iconsDir.delete(); 1358 iconsDir.mkdirs(); 1359 if (!iconsDir.isDirectory()) { 1360 throw new IOException("Unable to create directory " + iconsDir); 1361 } 1362 if (DEBUG) { 1363 Slog.i(TAG, "Created icons directory at " + iconsDir.getAbsolutePath()); 1364 } 1365 } 1366 SELinux.restorecon(iconsDir); 1367 return iconsDir; 1368 } 1369 getIconsDir(String packageName, int userId)1370 private static File getIconsDir(String packageName, int userId) { 1371 return new File( 1372 new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR), 1373 packageName); 1374 } 1375 bytesFromBitmapFile(Path path)1376 private static byte[] bytesFromBitmapFile(Path path) throws IOException { 1377 if (path == null) { 1378 return null; 1379 } 1380 // Technically we could just read the bytes, but we want to be sure we store the 1381 // right format. 1382 return bytesFromBitmap(BitmapFactory.decodeFile(path.toString())); 1383 } 1384 1385 @Nullable getPackageNameFromIntent(@ullable Intent intent)1386 private static String getPackageNameFromIntent(@Nullable Intent intent) { 1387 if (intent == null) { 1388 return null; 1389 } 1390 if (intent.getPackage() != null) { 1391 return intent.getPackage(); 1392 } 1393 if (intent.getComponent() != null) { 1394 return intent.getComponent().getPackageName(); 1395 } 1396 return null; 1397 } 1398 1399 /** 1400 * Creates serializable archived activities from existing ArchiveState. 1401 */ createArchivedActivities(ArchiveState archiveState)1402 static ArchivedActivityParcel[] createArchivedActivities(ArchiveState archiveState) 1403 throws IOException { 1404 var infos = archiveState.getActivityInfos(); 1405 if (infos == null || infos.isEmpty()) { 1406 throw new IllegalArgumentException("No activities in archive state"); 1407 } 1408 1409 List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size()); 1410 for (int i = 0, size = infos.size(); i < size; ++i) { 1411 var info = infos.get(i); 1412 if (info == null) { 1413 continue; 1414 } 1415 var archivedActivity = new ArchivedActivityParcel(); 1416 archivedActivity.title = info.getTitle(); 1417 archivedActivity.originalComponentName = info.getOriginalComponentName(); 1418 archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap()); 1419 archivedActivity.monochromeIconBitmap = bytesFromBitmapFile( 1420 info.getMonochromeIconBitmap()); 1421 activities.add(archivedActivity); 1422 } 1423 1424 if (activities.isEmpty()) { 1425 throw new IllegalArgumentException( 1426 "Failed to extract title and icon of main activities"); 1427 } 1428 1429 return activities.toArray(new ArchivedActivityParcel[activities.size()]); 1430 } 1431 1432 /** 1433 * Creates serializable archived activities from launcher activities. 1434 */ createArchivedActivities(List<LauncherActivityInfo> infos, int iconSize)1435 static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos, 1436 int iconSize) throws IOException { 1437 if (infos == null || infos.isEmpty()) { 1438 throw new IllegalArgumentException("No launcher activities"); 1439 } 1440 1441 List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size()); 1442 for (int i = 0, size = infos.size(); i < size; ++i) { 1443 var info = infos.get(i); 1444 if (info == null) { 1445 continue; 1446 } 1447 var archivedActivity = new ArchivedActivityParcel(); 1448 archivedActivity.title = info.getLabel().toString(); 1449 archivedActivity.originalComponentName = info.getComponentName(); 1450 archivedActivity.iconBitmap = info.getActivityInfo().getIconResource() == 0 ? null : 1451 bytesFromBitmap(drawableToBitmap(info.getIcon(/* density= */ 0), iconSize)); 1452 // TODO(b/298452477) Handle monochrome icons. 1453 archivedActivity.monochromeIconBitmap = null; 1454 activities.add(archivedActivity); 1455 } 1456 1457 if (activities.isEmpty()) { 1458 throw new IllegalArgumentException( 1459 "Failed to extract title and icon of main activities"); 1460 } 1461 1462 return activities.toArray(new ArchivedActivityParcel[activities.size()]); 1463 } 1464 1465 private class UnarchiveIntentSender extends IIntentSender.Stub { 1466 @Override send(int code, Intent intent, String resolvedType, IBinder whitelistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options)1467 public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, 1468 IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) 1469 throws RemoteException { 1470 int status = intent.getExtras().getInt(PackageInstaller.EXTRA_UNARCHIVE_STATUS, 1471 STATUS_PENDING_USER_ACTION); 1472 if (status == UNARCHIVAL_OK) { 1473 return; 1474 } 1475 Intent extraIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class); 1476 UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class); 1477 if (extraIntent != null && user != null 1478 && mAppStateHelper.isAppTopVisible( 1479 getCurrentLauncherPackageName(user.getIdentifier()))) { 1480 extraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1481 mContext.startActivityAsUser(extraIntent, user); 1482 } 1483 } 1484 } 1485 } 1486