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