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