1 /* 2 * Copyright (C) 2015 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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UserIdInt; 22 import android.content.Intent; 23 import android.content.pm.InstantAppInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageParser; 26 import android.graphics.Bitmap; 27 import android.graphics.BitmapFactory; 28 import android.graphics.Canvas; 29 import android.graphics.drawable.BitmapDrawable; 30 import android.graphics.drawable.Drawable; 31 import android.os.Binder; 32 import android.os.Environment; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.os.UserHandle; 37 import android.os.storage.StorageManager; 38 import android.provider.Settings; 39 import android.util.ArrayMap; 40 import android.util.AtomicFile; 41 import android.util.ByteStringUtils; 42 import android.util.PackageUtils; 43 import android.util.Slog; 44 import android.util.SparseArray; 45 import android.util.SparseBooleanArray; 46 import android.util.Xml; 47 48 import com.android.internal.annotations.GuardedBy; 49 import com.android.internal.os.BackgroundThread; 50 import com.android.internal.os.SomeArgs; 51 import com.android.internal.util.ArrayUtils; 52 import com.android.internal.util.XmlUtils; 53 54 import libcore.io.IoUtils; 55 56 import org.xmlpull.v1.XmlPullParser; 57 import org.xmlpull.v1.XmlPullParserException; 58 import org.xmlpull.v1.XmlSerializer; 59 60 import java.io.File; 61 import java.io.FileInputStream; 62 import java.io.FileNotFoundException; 63 import java.io.FileOutputStream; 64 import java.io.IOException; 65 import java.nio.charset.StandardCharsets; 66 import java.security.SecureRandom; 67 import java.util.ArrayList; 68 import java.util.List; 69 import java.util.Locale; 70 import java.util.Set; 71 import java.util.function.Predicate; 72 73 /** 74 * This class is a part of the package manager service that is responsible 75 * for managing data associated with instant apps such as cached uninstalled 76 * instant apps and instant apps' cookies. In addition it is responsible for 77 * pruning installed instant apps and meta-data for uninstalled instant apps 78 * when free space is needed. 79 */ 80 class InstantAppRegistry { 81 private static final boolean DEBUG = false; 82 83 private static final String LOG_TAG = "InstantAppRegistry"; 84 85 static final long DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD = 86 DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */ 87 88 private static final long DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD = 89 DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */ 90 91 static final long DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD = 92 DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */ 93 94 private static final long DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD = 95 DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */ 96 97 private static final String INSTANT_APPS_FOLDER = "instant"; 98 private static final String INSTANT_APP_ICON_FILE = "icon.png"; 99 private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_"; 100 private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat"; 101 private static final String INSTANT_APP_METADATA_FILE = "metadata.xml"; 102 private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id"; 103 104 private static final String TAG_PACKAGE = "package"; 105 private static final String TAG_PERMISSIONS = "permissions"; 106 private static final String TAG_PERMISSION = "permission"; 107 108 private static final String ATTR_LABEL = "label"; 109 private static final String ATTR_NAME = "name"; 110 private static final String ATTR_GRANTED = "granted"; 111 112 private final PackageManagerService mService; 113 private final CookiePersistence mCookiePersistence; 114 115 /** State for uninstalled instant apps */ 116 @GuardedBy("mService.mPackages") 117 private SparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps; 118 119 /** 120 * Automatic grants for access to instant app metadata. 121 * The key is the target application UID. 122 * The value is a set of instant app UIDs. 123 * UserID -> TargetAppId -> InstantAppId 124 */ 125 @GuardedBy("mService.mPackages") 126 private SparseArray<SparseArray<SparseBooleanArray>> mInstantGrants; 127 128 /** The set of all installed instant apps. UserID -> AppID */ 129 @GuardedBy("mService.mPackages") 130 private SparseArray<SparseBooleanArray> mInstalledInstantAppUids; 131 132 public InstantAppRegistry(PackageManagerService service) { 133 mService = service; 134 mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper()); 135 } 136 137 public byte[] getInstantAppCookieLPw(@NonNull String packageName, 138 @UserIdInt int userId) { 139 // Only installed packages can get their own cookie 140 PackageParser.Package pkg = mService.mPackages.get(packageName); 141 if (pkg == null) { 142 return null; 143 } 144 145 byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId); 146 if (pendingCookie != null) { 147 return pendingCookie; 148 } 149 File cookieFile = peekInstantCookieFile(packageName, userId); 150 if (cookieFile != null && cookieFile.exists()) { 151 try { 152 return IoUtils.readFileAsByteArray(cookieFile.toString()); 153 } catch (IOException e) { 154 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile); 155 } 156 } 157 return null; 158 } 159 160 public boolean setInstantAppCookieLPw(@NonNull String packageName, 161 @Nullable byte[] cookie, @UserIdInt int userId) { 162 if (cookie != null && cookie.length > 0) { 163 final int maxCookieSize = mService.mContext.getPackageManager() 164 .getInstantAppCookieMaxBytes(); 165 if (cookie.length > maxCookieSize) { 166 Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size " 167 + cookie.length + " bytes while max size is " + maxCookieSize); 168 return false; 169 } 170 } 171 172 // Only an installed package can set its own cookie 173 PackageParser.Package pkg = mService.mPackages.get(packageName); 174 if (pkg == null) { 175 return false; 176 } 177 178 mCookiePersistence.schedulePersistLPw(userId, pkg, cookie); 179 return true; 180 } 181 182 private void persistInstantApplicationCookie(@Nullable byte[] cookie, 183 @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) { 184 synchronized (mService.mPackages) { 185 File appDir = getInstantApplicationDir(packageName, userId); 186 if (!appDir.exists() && !appDir.mkdirs()) { 187 Slog.e(LOG_TAG, "Cannot create instant app cookie directory"); 188 return; 189 } 190 191 if (cookieFile.exists() && !cookieFile.delete()) { 192 Slog.e(LOG_TAG, "Cannot delete instant app cookie file"); 193 } 194 195 // No cookie or an empty one means delete - done 196 if (cookie == null || cookie.length <= 0) { 197 return; 198 } 199 } 200 try (FileOutputStream fos = new FileOutputStream(cookieFile)) { 201 fos.write(cookie, 0, cookie.length); 202 } catch (IOException e) { 203 Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e); 204 } 205 } 206 207 public Bitmap getInstantAppIconLPw(@NonNull String packageName, 208 @UserIdInt int userId) { 209 File iconFile = new File(getInstantApplicationDir(packageName, userId), 210 INSTANT_APP_ICON_FILE); 211 if (iconFile.exists()) { 212 return BitmapFactory.decodeFile(iconFile.toString()); 213 } 214 return null; 215 } 216 217 public String getInstantAppAndroidIdLPw(@NonNull String packageName, 218 @UserIdInt int userId) { 219 File idFile = new File(getInstantApplicationDir(packageName, userId), 220 INSTANT_APP_ANDROID_ID_FILE); 221 if (idFile.exists()) { 222 try { 223 return IoUtils.readFileAsString(idFile.getAbsolutePath()); 224 } catch (IOException e) { 225 Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e); 226 } 227 } 228 return generateInstantAppAndroidIdLPw(packageName, userId); 229 } 230 231 private String generateInstantAppAndroidIdLPw(@NonNull String packageName, 232 @UserIdInt int userId) { 233 byte[] randomBytes = new byte[8]; 234 new SecureRandom().nextBytes(randomBytes); 235 String id = ByteStringUtils.toHexString(randomBytes).toLowerCase(Locale.US); 236 File appDir = getInstantApplicationDir(packageName, userId); 237 if (!appDir.exists() && !appDir.mkdirs()) { 238 Slog.e(LOG_TAG, "Cannot create instant app cookie directory"); 239 return id; 240 } 241 File idFile = new File(getInstantApplicationDir(packageName, userId), 242 INSTANT_APP_ANDROID_ID_FILE); 243 try (FileOutputStream fos = new FileOutputStream(idFile)) { 244 fos.write(id.getBytes()); 245 } catch (IOException e) { 246 Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e); 247 } 248 return id; 249 250 } 251 252 public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) { 253 List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId); 254 List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId); 255 if (installedApps != null) { 256 if (uninstalledApps != null) { 257 installedApps.addAll(uninstalledApps); 258 } 259 return installedApps; 260 } 261 return uninstalledApps; 262 } 263 264 public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) { 265 PackageSetting ps = (PackageSetting) pkg.mExtras; 266 if (ps == null) { 267 return; 268 } 269 270 for (int userId : userIds) { 271 // Ignore not installed apps 272 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) { 273 continue; 274 } 275 276 // Propagate permissions before removing any state 277 propagateInstantAppPermissionsIfNeeded(pkg, userId); 278 279 // Track instant apps 280 if (ps.getInstantApp(userId)) { 281 addInstantAppLPw(userId, ps.appId); 282 } 283 284 // Remove the in-memory state 285 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> 286 state.mInstantAppInfo.getPackageName().equals(pkg.packageName), 287 userId); 288 289 // Remove the on-disk state except the cookie 290 File instantAppDir = getInstantApplicationDir(pkg.packageName, userId); 291 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete(); 292 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete(); 293 294 // If app signature changed - wipe the cookie 295 File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId); 296 if (currentCookieFile == null) { 297 continue; 298 } 299 300 String cookieName = currentCookieFile.getName(); 301 String currentCookieSha256 = 302 cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(), 303 cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length()); 304 305 // Before we used only the first signature to compute the SHA 256 but some 306 // apps could be singed by multiple certs and the cert order is undefined. 307 // We prefer the modern computation procedure where all certs are taken 308 // into account but also allow the value from the old computation to avoid 309 // data loss. 310 if (pkg.mSigningDetails.checkCapability(currentCookieSha256, 311 PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { 312 return; 313 } 314 315 // For backwards compatibility we accept match based on any signature, since we may have 316 // recorded only the first for multiply-signed packages 317 final String[] signaturesSha256Digests = 318 PackageUtils.computeSignaturesSha256Digests(pkg.mSigningDetails.signatures); 319 for (String s : signaturesSha256Digests) { 320 if (s.equals(currentCookieSha256)) { 321 return; 322 } 323 } 324 325 // Sorry, you are out of luck - different signatures - nuke data 326 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName 327 + " changed - dropping cookie"); 328 // Make sure a pending write for the old signed app is cancelled 329 mCookiePersistence.cancelPendingPersistLPw(pkg, userId); 330 currentCookieFile.delete(); 331 } 332 } 333 334 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg, 335 @NonNull int[] userIds) { 336 PackageSetting ps = (PackageSetting) pkg.mExtras; 337 if (ps == null) { 338 return; 339 } 340 341 for (int userId : userIds) { 342 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) { 343 continue; 344 } 345 346 if (ps.getInstantApp(userId)) { 347 // Add a record for an uninstalled instant app 348 addUninstalledInstantAppLPw(pkg, userId); 349 removeInstantAppLPw(userId, ps.appId); 350 } else { 351 // Deleting an app prunes all instant state such as cookie 352 deleteDir(getInstantApplicationDir(pkg.packageName, userId)); 353 mCookiePersistence.cancelPendingPersistLPw(pkg, userId); 354 removeAppLPw(userId, ps.appId); 355 } 356 } 357 } 358 359 public void onUserRemovedLPw(int userId) { 360 if (mUninstalledInstantApps != null) { 361 mUninstalledInstantApps.remove(userId); 362 if (mUninstalledInstantApps.size() <= 0) { 363 mUninstalledInstantApps = null; 364 } 365 } 366 if (mInstalledInstantAppUids != null) { 367 mInstalledInstantAppUids.remove(userId); 368 if (mInstalledInstantAppUids.size() <= 0) { 369 mInstalledInstantAppUids = null; 370 } 371 } 372 if (mInstantGrants != null) { 373 mInstantGrants.remove(userId); 374 if (mInstantGrants.size() <= 0) { 375 mInstantGrants = null; 376 } 377 } 378 deleteDir(getInstantApplicationsDir(userId)); 379 } 380 381 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId, 382 int instantAppId) { 383 if (mInstantGrants == null) { 384 return false; 385 } 386 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 387 if (targetAppList == null) { 388 return false; 389 } 390 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId); 391 if (instantGrantList == null) { 392 return false; 393 } 394 return instantGrantList.get(instantAppId); 395 } 396 397 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent, 398 int targetAppId, int instantAppId) { 399 if (mInstalledInstantAppUids == null) { 400 return; // no instant apps installed; no need to grant 401 } 402 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 403 if (instantAppList == null || !instantAppList.get(instantAppId)) { 404 return; // instant app id isn't installed; no need to grant 405 } 406 if (instantAppList.get(targetAppId)) { 407 return; // target app id is an instant app; no need to grant 408 } 409 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { 410 final Set<String> categories = intent.getCategories(); 411 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) { 412 return; // launched via VIEW/BROWSABLE intent; no need to grant 413 } 414 } 415 if (mInstantGrants == null) { 416 mInstantGrants = new SparseArray<>(); 417 } 418 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 419 if (targetAppList == null) { 420 targetAppList = new SparseArray<>(); 421 mInstantGrants.put(userId, targetAppList); 422 } 423 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId); 424 if (instantGrantList == null) { 425 instantGrantList = new SparseBooleanArray(); 426 targetAppList.put(targetAppId, instantGrantList); 427 } 428 instantGrantList.put(instantAppId, true /*granted*/); 429 } 430 431 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) { 432 if (mInstalledInstantAppUids == null) { 433 mInstalledInstantAppUids = new SparseArray<>(); 434 } 435 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 436 if (instantAppList == null) { 437 instantAppList = new SparseBooleanArray(); 438 mInstalledInstantAppUids.put(userId, instantAppList); 439 } 440 instantAppList.put(instantAppId, true /*installed*/); 441 } 442 443 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) { 444 // remove from the installed list 445 if (mInstalledInstantAppUids == null) { 446 return; // no instant apps on the system 447 } 448 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 449 if (instantAppList == null) { 450 return; 451 } 452 453 instantAppList.delete(instantAppId); 454 455 // remove any grants 456 if (mInstantGrants == null) { 457 return; // no grants on the system 458 } 459 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 460 if (targetAppList == null) { 461 return; // no grants for this user 462 } 463 for (int i = targetAppList.size() - 1; i >= 0; --i) { 464 targetAppList.valueAt(i).delete(instantAppId); 465 } 466 } 467 468 private void removeAppLPw(@UserIdInt int userId, int targetAppId) { 469 // remove from the installed list 470 if (mInstantGrants == null) { 471 return; // no grants on the system 472 } 473 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 474 if (targetAppList == null) { 475 return; // no grants for this user 476 } 477 targetAppList.delete(targetAppId); 478 } 479 480 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg, 481 @UserIdInt int userId) { 482 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage( 483 pkg, userId, false); 484 if (uninstalledApp == null) { 485 return; 486 } 487 if (mUninstalledInstantApps == null) { 488 mUninstalledInstantApps = new SparseArray<>(); 489 } 490 List<UninstalledInstantAppState> uninstalledAppStates = 491 mUninstalledInstantApps.get(userId); 492 if (uninstalledAppStates == null) { 493 uninstalledAppStates = new ArrayList<>(); 494 mUninstalledInstantApps.put(userId, uninstalledAppStates); 495 } 496 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState( 497 uninstalledApp, System.currentTimeMillis()); 498 uninstalledAppStates.add(uninstalledAppState); 499 500 writeUninstalledInstantAppMetadata(uninstalledApp, userId); 501 writeInstantApplicationIconLPw(pkg, userId); 502 } 503 504 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg, 505 @UserIdInt int userId) { 506 File appDir = getInstantApplicationDir(pkg.packageName, userId); 507 if (!appDir.exists()) { 508 return; 509 } 510 511 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager()); 512 513 final Bitmap bitmap; 514 if (icon instanceof BitmapDrawable) { 515 bitmap = ((BitmapDrawable) icon).getBitmap(); 516 } else { 517 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), 518 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 519 Canvas canvas = new Canvas(bitmap); 520 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 521 icon.draw(canvas); 522 } 523 524 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId), 525 INSTANT_APP_ICON_FILE); 526 527 try (FileOutputStream out = new FileOutputStream(iconFile)) { 528 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 529 } catch (Exception e) { 530 Slog.e(LOG_TAG, "Error writing instant app icon", e); 531 } 532 } 533 534 boolean hasInstantApplicationMetadataLPr(String packageName, int userId) { 535 return hasUninstalledInstantAppStateLPr(packageName, userId) 536 || hasInstantAppMetadataLPr(packageName, userId); 537 } 538 539 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName, 540 @UserIdInt int userId) { 541 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> 542 state.mInstantAppInfo.getPackageName().equals(packageName), 543 userId); 544 545 File instantAppDir = getInstantApplicationDir(packageName, userId); 546 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete(); 547 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete(); 548 new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete(); 549 File cookie = peekInstantCookieFile(packageName, userId); 550 if (cookie != null) { 551 cookie.delete(); 552 } 553 } 554 555 private void removeUninstalledInstantAppStateLPw( 556 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) { 557 if (mUninstalledInstantApps == null) { 558 return; 559 } 560 List<UninstalledInstantAppState> uninstalledAppStates = 561 mUninstalledInstantApps.get(userId); 562 if (uninstalledAppStates == null) { 563 return; 564 } 565 final int appCount = uninstalledAppStates.size(); 566 for (int i = appCount - 1; i >= 0; --i) { 567 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 568 if (!criteria.test(uninstalledAppState)) { 569 continue; 570 } 571 uninstalledAppStates.remove(i); 572 if (uninstalledAppStates.isEmpty()) { 573 mUninstalledInstantApps.remove(userId); 574 if (mUninstalledInstantApps.size() <= 0) { 575 mUninstalledInstantApps = null; 576 } 577 return; 578 } 579 } 580 } 581 582 private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) { 583 if (mUninstalledInstantApps == null) { 584 return false; 585 } 586 final List<UninstalledInstantAppState> uninstalledAppStates = 587 mUninstalledInstantApps.get(userId); 588 if (uninstalledAppStates == null) { 589 return false; 590 } 591 final int appCount = uninstalledAppStates.size(); 592 for (int i = 0; i < appCount; i++) { 593 final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 594 if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) { 595 return true; 596 } 597 } 598 return false; 599 } 600 601 private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) { 602 final File instantAppDir = getInstantApplicationDir(packageName, userId); 603 return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists() 604 || new File(instantAppDir, INSTANT_APP_ICON_FILE).exists() 605 || new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).exists() 606 || peekInstantCookieFile(packageName, userId) != null; 607 } 608 609 void pruneInstantApps() { 610 final long maxInstalledCacheDuration = Settings.Global.getLong( 611 mService.mContext.getContentResolver(), 612 Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, 613 DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); 614 615 final long maxUninstalledCacheDuration = Settings.Global.getLong( 616 mService.mContext.getContentResolver(), 617 Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, 618 DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); 619 620 try { 621 pruneInstantApps(Long.MAX_VALUE, 622 maxInstalledCacheDuration, maxUninstalledCacheDuration); 623 } catch (IOException e) { 624 Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e); 625 } 626 } 627 628 boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) { 629 try { 630 return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE); 631 } catch (IOException e) { 632 Slog.e(LOG_TAG, "Error pruning installed instant apps", e); 633 return false; 634 } 635 } 636 637 boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) { 638 try { 639 return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration); 640 } catch (IOException e) { 641 Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e); 642 return false; 643 } 644 } 645 646 /** 647 * Prunes instant apps until there is enough <code>neededSpace</code>. Both 648 * installed and uninstalled instant apps are pruned that are older than 649 * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code> 650 * respectively. All times are in milliseconds. 651 * 652 * @param neededSpace The space to ensure is free. 653 * @param maxInstalledCacheDuration The max duration for caching installed apps in millis. 654 * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis. 655 * @return Whether enough space was freed. 656 * 657 * @throws IOException 658 */ 659 private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration, 660 long maxUninstalledCacheDuration) throws IOException { 661 final StorageManager storage = mService.mContext.getSystemService(StorageManager.class); 662 final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL); 663 664 if (file.getUsableSpace() >= neededSpace) { 665 return true; 666 } 667 668 List<String> packagesToDelete = null; 669 670 final int[] allUsers; 671 final long now = System.currentTimeMillis(); 672 673 // Prune first installed instant apps 674 synchronized (mService.mPackages) { 675 allUsers = PackageManagerService.sUserManager.getUserIds(); 676 677 final int packageCount = mService.mPackages.size(); 678 for (int i = 0; i < packageCount; i++) { 679 final PackageParser.Package pkg = mService.mPackages.valueAt(i); 680 if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) { 681 continue; 682 } 683 if (!(pkg.mExtras instanceof PackageSetting)) { 684 continue; 685 } 686 final PackageSetting ps = (PackageSetting) pkg.mExtras; 687 boolean installedOnlyAsInstantApp = false; 688 for (int userId : allUsers) { 689 if (ps.getInstalled(userId)) { 690 if (ps.getInstantApp(userId)) { 691 installedOnlyAsInstantApp = true; 692 } else { 693 installedOnlyAsInstantApp = false; 694 break; 695 } 696 } 697 } 698 if (installedOnlyAsInstantApp) { 699 if (packagesToDelete == null) { 700 packagesToDelete = new ArrayList<>(); 701 } 702 packagesToDelete.add(pkg.packageName); 703 } 704 } 705 706 if (packagesToDelete != null) { 707 packagesToDelete.sort((String lhs, String rhs) -> { 708 final PackageParser.Package lhsPkg = mService.mPackages.get(lhs); 709 final PackageParser.Package rhsPkg = mService.mPackages.get(rhs); 710 if (lhsPkg == null && rhsPkg == null) { 711 return 0; 712 } else if (lhsPkg == null) { 713 return -1; 714 } else if (rhsPkg == null) { 715 return 1; 716 } else { 717 if (lhsPkg.getLatestPackageUseTimeInMills() > 718 rhsPkg.getLatestPackageUseTimeInMills()) { 719 return 1; 720 } else if (lhsPkg.getLatestPackageUseTimeInMills() < 721 rhsPkg.getLatestPackageUseTimeInMills()) { 722 return -1; 723 } else { 724 if (lhsPkg.mExtras instanceof PackageSetting 725 && rhsPkg.mExtras instanceof PackageSetting) { 726 final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras; 727 final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras; 728 if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) { 729 return 1; 730 } else { 731 return -1; 732 } 733 } else { 734 return 0; 735 } 736 } 737 } 738 }); 739 } 740 } 741 742 if (packagesToDelete != null) { 743 final int packageCount = packagesToDelete.size(); 744 for (int i = 0; i < packageCount; i++) { 745 final String packageToDelete = packagesToDelete.get(i); 746 if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST, 747 UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS) 748 == PackageManager.DELETE_SUCCEEDED) { 749 if (file.getUsableSpace() >= neededSpace) { 750 return true; 751 } 752 } 753 } 754 } 755 756 // Prune uninstalled instant apps 757 synchronized (mService.mPackages) { 758 // TODO: Track last used time for uninstalled instant apps for better pruning 759 for (int userId : UserManagerService.getInstance().getUserIds()) { 760 // Prune in-memory state 761 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> { 762 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp; 763 return (elapsedCachingMillis > maxUninstalledCacheDuration); 764 }, userId); 765 766 // Prune on-disk state 767 File instantAppsDir = getInstantApplicationsDir(userId); 768 if (!instantAppsDir.exists()) { 769 continue; 770 } 771 File[] files = instantAppsDir.listFiles(); 772 if (files == null) { 773 continue; 774 } 775 for (File instantDir : files) { 776 if (!instantDir.isDirectory()) { 777 continue; 778 } 779 780 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE); 781 if (!metadataFile.exists()) { 782 continue; 783 } 784 785 final long elapsedCachingMillis = System.currentTimeMillis() 786 - metadataFile.lastModified(); 787 if (elapsedCachingMillis > maxUninstalledCacheDuration) { 788 deleteDir(instantDir); 789 if (file.getUsableSpace() >= neededSpace) { 790 return true; 791 } 792 } 793 } 794 } 795 } 796 797 return false; 798 } 799 800 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr( 801 @UserIdInt int userId) { 802 List<InstantAppInfo> result = null; 803 804 final int packageCount = mService.mPackages.size(); 805 for (int i = 0; i < packageCount; i++) { 806 final PackageParser.Package pkg = mService.mPackages.valueAt(i); 807 final PackageSetting ps = (PackageSetting) pkg.mExtras; 808 if (ps == null || !ps.getInstantApp(userId)) { 809 continue; 810 } 811 final InstantAppInfo info = createInstantAppInfoForPackage( 812 pkg, userId, true); 813 if (info == null) { 814 continue; 815 } 816 if (result == null) { 817 result = new ArrayList<>(); 818 } 819 result.add(info); 820 } 821 822 return result; 823 } 824 825 private @NonNull 826 InstantAppInfo createInstantAppInfoForPackage( 827 @NonNull PackageParser.Package pkg, @UserIdInt int userId, 828 boolean addApplicationInfo) { 829 PackageSetting ps = (PackageSetting) pkg.mExtras; 830 if (ps == null) { 831 return null; 832 } 833 if (!ps.getInstalled(userId)) { 834 return null; 835 } 836 837 String[] requestedPermissions = new String[pkg.requestedPermissions.size()]; 838 pkg.requestedPermissions.toArray(requestedPermissions); 839 840 Set<String> permissions = ps.getPermissionsState().getPermissions(userId); 841 String[] grantedPermissions = new String[permissions.size()]; 842 permissions.toArray(grantedPermissions); 843 844 if (addApplicationInfo) { 845 return new InstantAppInfo(pkg.applicationInfo, 846 requestedPermissions, grantedPermissions); 847 } else { 848 return new InstantAppInfo(pkg.applicationInfo.packageName, 849 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()), 850 requestedPermissions, grantedPermissions); 851 } 852 } 853 854 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr( 855 @UserIdInt int userId) { 856 List<UninstalledInstantAppState> uninstalledAppStates = 857 getUninstalledInstantAppStatesLPr(userId); 858 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) { 859 return null; 860 } 861 862 List<InstantAppInfo> uninstalledApps = null; 863 final int stateCount = uninstalledAppStates.size(); 864 for (int i = 0; i < stateCount; i++) { 865 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 866 if (uninstalledApps == null) { 867 uninstalledApps = new ArrayList<>(); 868 } 869 uninstalledApps.add(uninstalledAppState.mInstantAppInfo); 870 } 871 return uninstalledApps; 872 } 873 874 private void propagateInstantAppPermissionsIfNeeded(@NonNull PackageParser.Package pkg, 875 @UserIdInt int userId) { 876 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo( 877 pkg.packageName, userId); 878 if (appInfo == null) { 879 return; 880 } 881 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) { 882 return; 883 } 884 final long identity = Binder.clearCallingIdentity(); 885 try { 886 for (String grantedPermission : appInfo.getGrantedPermissions()) { 887 final boolean propagatePermission = 888 mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission); 889 if (propagatePermission && pkg.requestedPermissions.contains(grantedPermission)) { 890 mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId); 891 } 892 } 893 } finally { 894 Binder.restoreCallingIdentity(identity); 895 } 896 } 897 898 private @NonNull 899 InstantAppInfo peekOrParseUninstalledInstantAppInfo( 900 @NonNull String packageName, @UserIdInt int userId) { 901 if (mUninstalledInstantApps != null) { 902 List<UninstalledInstantAppState> uninstalledAppStates = 903 mUninstalledInstantApps.get(userId); 904 if (uninstalledAppStates != null) { 905 final int appCount = uninstalledAppStates.size(); 906 for (int i = 0; i < appCount; i++) { 907 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 908 if (uninstalledAppState.mInstantAppInfo 909 .getPackageName().equals(packageName)) { 910 return uninstalledAppState.mInstantAppInfo; 911 } 912 } 913 } 914 } 915 916 File metadataFile = new File(getInstantApplicationDir(packageName, userId), 917 INSTANT_APP_METADATA_FILE); 918 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile); 919 if (uninstalledAppState == null) { 920 return null; 921 } 922 923 return uninstalledAppState.mInstantAppInfo; 924 } 925 926 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr( 927 @UserIdInt int userId) { 928 List<UninstalledInstantAppState> uninstalledAppStates = null; 929 if (mUninstalledInstantApps != null) { 930 uninstalledAppStates = mUninstalledInstantApps.get(userId); 931 if (uninstalledAppStates != null) { 932 return uninstalledAppStates; 933 } 934 } 935 936 File instantAppsDir = getInstantApplicationsDir(userId); 937 if (instantAppsDir.exists()) { 938 File[] files = instantAppsDir.listFiles(); 939 if (files != null) { 940 for (File instantDir : files) { 941 if (!instantDir.isDirectory()) { 942 continue; 943 } 944 File metadataFile = new File(instantDir, 945 INSTANT_APP_METADATA_FILE); 946 UninstalledInstantAppState uninstalledAppState = 947 parseMetadataFile(metadataFile); 948 if (uninstalledAppState == null) { 949 continue; 950 } 951 if (uninstalledAppStates == null) { 952 uninstalledAppStates = new ArrayList<>(); 953 } 954 uninstalledAppStates.add(uninstalledAppState); 955 } 956 } 957 } 958 959 if (uninstalledAppStates != null) { 960 if (mUninstalledInstantApps == null) { 961 mUninstalledInstantApps = new SparseArray<>(); 962 } 963 mUninstalledInstantApps.put(userId, uninstalledAppStates); 964 } 965 966 return uninstalledAppStates; 967 } 968 969 private static @Nullable UninstalledInstantAppState parseMetadataFile( 970 @NonNull File metadataFile) { 971 if (!metadataFile.exists()) { 972 return null; 973 } 974 FileInputStream in; 975 try { 976 in = new AtomicFile(metadataFile).openRead(); 977 } catch (FileNotFoundException fnfe) { 978 Slog.i(LOG_TAG, "No instant metadata file"); 979 return null; 980 } 981 982 final File instantDir = metadataFile.getParentFile(); 983 final long timestamp = metadataFile.lastModified(); 984 final String packageName = instantDir.getName(); 985 986 try { 987 XmlPullParser parser = Xml.newPullParser(); 988 parser.setInput(in, StandardCharsets.UTF_8.name()); 989 return new UninstalledInstantAppState( 990 parseMetadata(parser, packageName), timestamp); 991 } catch (XmlPullParserException | IOException e) { 992 throw new IllegalStateException("Failed parsing instant" 993 + " metadata file: " + metadataFile, e); 994 } finally { 995 IoUtils.closeQuietly(in); 996 } 997 } 998 999 private static @NonNull File computeInstantCookieFile(@NonNull String packageName, 1000 @NonNull String sha256Digest, @UserIdInt int userId) { 1001 final File appDir = getInstantApplicationDir(packageName, userId); 1002 final String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX 1003 + sha256Digest + INSTANT_APP_COOKIE_FILE_SIFFIX; 1004 return new File(appDir, cookieFile); 1005 } 1006 1007 private static @Nullable File peekInstantCookieFile(@NonNull String packageName, 1008 @UserIdInt int userId) { 1009 File appDir = getInstantApplicationDir(packageName, userId); 1010 if (!appDir.exists()) { 1011 return null; 1012 } 1013 File[] files = appDir.listFiles(); 1014 if (files == null) { 1015 return null; 1016 } 1017 for (File file : files) { 1018 if (!file.isDirectory() 1019 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX) 1020 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) { 1021 return file; 1022 } 1023 } 1024 return null; 1025 } 1026 1027 private static @Nullable 1028 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser, 1029 @NonNull String packageName) 1030 throws IOException, XmlPullParserException { 1031 final int outerDepth = parser.getDepth(); 1032 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1033 if (TAG_PACKAGE.equals(parser.getName())) { 1034 return parsePackage(parser, packageName); 1035 } 1036 } 1037 return null; 1038 } 1039 1040 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser, 1041 @NonNull String packageName) 1042 throws IOException, XmlPullParserException { 1043 String label = parser.getAttributeValue(null, ATTR_LABEL); 1044 1045 List<String> outRequestedPermissions = new ArrayList<>(); 1046 List<String> outGrantedPermissions = new ArrayList<>(); 1047 1048 final int outerDepth = parser.getDepth(); 1049 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1050 if (TAG_PERMISSIONS.equals(parser.getName())) { 1051 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions); 1052 } 1053 } 1054 1055 String[] requestedPermissions = new String[outRequestedPermissions.size()]; 1056 outRequestedPermissions.toArray(requestedPermissions); 1057 1058 String[] grantedPermissions = new String[outGrantedPermissions.size()]; 1059 outGrantedPermissions.toArray(grantedPermissions); 1060 1061 return new InstantAppInfo(packageName, label, 1062 requestedPermissions, grantedPermissions); 1063 } 1064 1065 private static void parsePermissions(@NonNull XmlPullParser parser, 1066 @NonNull List<String> outRequestedPermissions, 1067 @NonNull List<String> outGrantedPermissions) 1068 throws IOException, XmlPullParserException { 1069 final int outerDepth = parser.getDepth(); 1070 while (XmlUtils.nextElementWithin(parser,outerDepth)) { 1071 if (TAG_PERMISSION.equals(parser.getName())) { 1072 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME); 1073 outRequestedPermissions.add(permission); 1074 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) { 1075 outGrantedPermissions.add(permission); 1076 } 1077 } 1078 } 1079 } 1080 1081 private void writeUninstalledInstantAppMetadata( 1082 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) { 1083 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId); 1084 if (!appDir.exists() && !appDir.mkdirs()) { 1085 return; 1086 } 1087 1088 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE); 1089 1090 AtomicFile destination = new AtomicFile(metadataFile); 1091 FileOutputStream out = null; 1092 try { 1093 out = destination.startWrite(); 1094 1095 XmlSerializer serializer = Xml.newSerializer(); 1096 serializer.setOutput(out, StandardCharsets.UTF_8.name()); 1097 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 1098 1099 serializer.startDocument(null, true); 1100 1101 serializer.startTag(null, TAG_PACKAGE); 1102 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel( 1103 mService.mContext.getPackageManager()).toString()); 1104 1105 serializer.startTag(null, TAG_PERMISSIONS); 1106 for (String permission : instantApp.getRequestedPermissions()) { 1107 serializer.startTag(null, TAG_PERMISSION); 1108 serializer.attribute(null, ATTR_NAME, permission); 1109 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) { 1110 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true)); 1111 } 1112 serializer.endTag(null, TAG_PERMISSION); 1113 } 1114 serializer.endTag(null, TAG_PERMISSIONS); 1115 1116 serializer.endTag(null, TAG_PACKAGE); 1117 1118 serializer.endDocument(); 1119 destination.finishWrite(out); 1120 } catch (Throwable t) { 1121 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t); 1122 destination.failWrite(out); 1123 } finally { 1124 IoUtils.closeQuietly(out); 1125 } 1126 } 1127 1128 private static @NonNull File getInstantApplicationsDir(int userId) { 1129 return new File(Environment.getUserSystemDirectory(userId), 1130 INSTANT_APPS_FOLDER); 1131 } 1132 1133 private static @NonNull File getInstantApplicationDir(String packageName, int userId) { 1134 return new File(getInstantApplicationsDir(userId), packageName); 1135 } 1136 1137 private static void deleteDir(@NonNull File dir) { 1138 File[] files = dir.listFiles(); 1139 if (files != null) { 1140 for (File file : files) { 1141 deleteDir(file); 1142 } 1143 } 1144 dir.delete(); 1145 } 1146 1147 private static final class UninstalledInstantAppState { 1148 final InstantAppInfo mInstantAppInfo; 1149 final long mTimestamp; 1150 1151 public UninstalledInstantAppState(InstantAppInfo instantApp, 1152 long timestamp) { 1153 mInstantAppInfo = instantApp; 1154 mTimestamp = timestamp; 1155 } 1156 } 1157 1158 private final class CookiePersistence extends Handler { 1159 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */ 1160 1161 // In case you wonder why we stash the cookies aside, we use 1162 // the user id for the message id and the package for the payload. 1163 // Handler allows removing messages by id and tag where the 1164 // tag is compared using ==. So to allow cancelling the 1165 // pending persistence for an app under a given user we use 1166 // the fact that package are cached by the system so the == 1167 // comparison would match and we end up with a way to cancel 1168 // persisting the cookie for a user and package. 1169 private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies 1170 = new SparseArray<>(); 1171 1172 public CookiePersistence(Looper looper) { 1173 super(looper); 1174 } 1175 1176 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg, 1177 @NonNull byte[] cookie) { 1178 // Before we used only the first signature to compute the SHA 256 but some 1179 // apps could be singed by multiple certs and the cert order is undefined. 1180 // We prefer the modern computation procedure where all certs are taken 1181 // into account and delete the file derived via the legacy hash computation. 1182 File newCookieFile = computeInstantCookieFile(pkg.packageName, 1183 PackageUtils.computeSignaturesSha256Digest(pkg.mSigningDetails.signatures), userId); 1184 if (!pkg.mSigningDetails.hasSignatures()) { 1185 Slog.wtf(LOG_TAG, "Parsed Instant App contains no valid signatures!"); 1186 } 1187 File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId); 1188 if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) { 1189 oldCookieFile.delete(); 1190 } 1191 cancelPendingPersistLPw(pkg, userId); 1192 addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile); 1193 sendMessageDelayed(obtainMessage(userId, pkg), 1194 PERSIST_COOKIE_DELAY_MILLIS); 1195 } 1196 1197 public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg, 1198 @UserIdInt int userId) { 1199 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser = 1200 mPendingPersistCookies.get(userId); 1201 if (pendingWorkForUser != null) { 1202 SomeArgs state = pendingWorkForUser.get(pkg); 1203 if (state != null) { 1204 return (byte[]) state.arg1; 1205 } 1206 } 1207 return null; 1208 } 1209 1210 public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg, 1211 @UserIdInt int userId) { 1212 removeMessages(userId, pkg); 1213 SomeArgs state = removePendingPersistCookieLPr(pkg, userId); 1214 if (state != null) { 1215 state.recycle(); 1216 } 1217 } 1218 1219 private void addPendingPersistCookieLPw(@UserIdInt int userId, 1220 @NonNull PackageParser.Package pkg, @NonNull byte[] cookie, 1221 @NonNull File cookieFile) { 1222 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser = 1223 mPendingPersistCookies.get(userId); 1224 if (pendingWorkForUser == null) { 1225 pendingWorkForUser = new ArrayMap<>(); 1226 mPendingPersistCookies.put(userId, pendingWorkForUser); 1227 } 1228 SomeArgs args = SomeArgs.obtain(); 1229 args.arg1 = cookie; 1230 args.arg2 = cookieFile; 1231 pendingWorkForUser.put(pkg, args); 1232 } 1233 1234 private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg, 1235 @UserIdInt int userId) { 1236 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser = 1237 mPendingPersistCookies.get(userId); 1238 SomeArgs state = null; 1239 if (pendingWorkForUser != null) { 1240 state = pendingWorkForUser.remove(pkg); 1241 if (pendingWorkForUser.isEmpty()) { 1242 mPendingPersistCookies.remove(userId); 1243 } 1244 } 1245 return state; 1246 } 1247 1248 @Override 1249 public void handleMessage(Message message) { 1250 int userId = message.what; 1251 PackageParser.Package pkg = (PackageParser.Package) message.obj; 1252 SomeArgs state = removePendingPersistCookieLPr(pkg, userId); 1253 if (state == null) { 1254 return; 1255 } 1256 byte[] cookie = (byte[]) state.arg1; 1257 File cookieFile = (File) state.arg2; 1258 state.recycle(); 1259 persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId); 1260 } 1261 } 1262 } 1263