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