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