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