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