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