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