InstantAppRegistry.java revision f935a703985a921447df023d6f91dd0bbf967d9b
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 File expectedCookeFile = computeInstantCookieFile(pkg, userId); 297 if (!currentCookieFile.equals(expectedCookeFile)) { 298 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName 299 + " changed - dropping cookie"); 300 // Make sure a pending write for the old signed app is cancelled 301 mCookiePersistence.cancelPendingPersistLPw(pkg, userId); 302 currentCookieFile.delete(); 303 } 304 } 305 } 306 307 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg, 308 @NonNull int[] userIds) { 309 PackageSetting ps = (PackageSetting) pkg.mExtras; 310 if (ps == null) { 311 return; 312 } 313 314 for (int userId : userIds) { 315 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) { 316 continue; 317 } 318 319 if (ps.getInstantApp(userId)) { 320 // Add a record for an uninstalled instant app 321 addUninstalledInstantAppLPw(pkg, userId); 322 removeInstantAppLPw(userId, ps.appId); 323 } else { 324 // Deleting an app prunes all instant state such as cookie 325 deleteDir(getInstantApplicationDir(pkg.packageName, userId)); 326 mCookiePersistence.cancelPendingPersistLPw(pkg, userId); 327 removeAppLPw(userId, ps.appId); 328 } 329 } 330 } 331 332 public void onUserRemovedLPw(int userId) { 333 if (mUninstalledInstantApps != null) { 334 mUninstalledInstantApps.remove(userId); 335 if (mUninstalledInstantApps.size() <= 0) { 336 mUninstalledInstantApps = null; 337 } 338 } 339 if (mInstalledInstantAppUids != null) { 340 mInstalledInstantAppUids.remove(userId); 341 if (mInstalledInstantAppUids.size() <= 0) { 342 mInstalledInstantAppUids = null; 343 } 344 } 345 if (mInstantGrants != null) { 346 mInstantGrants.remove(userId); 347 if (mInstantGrants.size() <= 0) { 348 mInstantGrants = null; 349 } 350 } 351 deleteDir(getInstantApplicationsDir(userId)); 352 } 353 354 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId, 355 int instantAppId) { 356 if (mInstantGrants == null) { 357 return false; 358 } 359 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 360 if (targetAppList == null) { 361 return false; 362 } 363 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId); 364 if (instantGrantList == null) { 365 return false; 366 } 367 return instantGrantList.get(instantAppId); 368 } 369 370 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent, 371 int targetAppId, int instantAppId) { 372 if (mInstalledInstantAppUids == null) { 373 return; // no instant apps installed; no need to grant 374 } 375 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 376 if (instantAppList == null || !instantAppList.get(instantAppId)) { 377 return; // instant app id isn't installed; no need to grant 378 } 379 if (instantAppList.get(targetAppId)) { 380 return; // target app id is an instant app; no need to grant 381 } 382 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { 383 final Set<String> categories = intent.getCategories(); 384 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) { 385 return; // launched via VIEW/BROWSABLE intent; no need to grant 386 } 387 } 388 if (mInstantGrants == null) { 389 mInstantGrants = new SparseArray<>(); 390 } 391 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 392 if (targetAppList == null) { 393 targetAppList = new SparseArray<>(); 394 mInstantGrants.put(userId, targetAppList); 395 } 396 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId); 397 if (instantGrantList == null) { 398 instantGrantList = new SparseBooleanArray(); 399 targetAppList.put(targetAppId, instantGrantList); 400 } 401 instantGrantList.put(instantAppId, true /*granted*/); 402 } 403 404 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) { 405 if (mInstalledInstantAppUids == null) { 406 mInstalledInstantAppUids = new SparseArray<>(); 407 } 408 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 409 if (instantAppList == null) { 410 instantAppList = new SparseBooleanArray(); 411 mInstalledInstantAppUids.put(userId, instantAppList); 412 } 413 instantAppList.put(instantAppId, true /*installed*/); 414 } 415 416 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) { 417 // remove from the installed list 418 if (mInstalledInstantAppUids == null) { 419 return; // no instant apps on the system 420 } 421 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 422 if (instantAppList == null) { 423 return; 424 } 425 426 instantAppList.delete(instantAppId); 427 428 // remove any grants 429 if (mInstantGrants == null) { 430 return; // no grants on the system 431 } 432 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 433 if (targetAppList == null) { 434 return; // no grants for this user 435 } 436 for (int i = targetAppList.size() - 1; i >= 0; --i) { 437 targetAppList.valueAt(i).delete(instantAppId); 438 } 439 } 440 441 private void removeAppLPw(@UserIdInt int userId, int targetAppId) { 442 // remove from the installed list 443 if (mInstantGrants == null) { 444 return; // no grants on the system 445 } 446 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 447 if (targetAppList == null) { 448 return; // no grants for this user 449 } 450 targetAppList.delete(targetAppId); 451 } 452 453 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg, 454 @UserIdInt int userId) { 455 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage( 456 pkg, userId, false); 457 if (uninstalledApp == null) { 458 return; 459 } 460 if (mUninstalledInstantApps == null) { 461 mUninstalledInstantApps = new SparseArray<>(); 462 } 463 List<UninstalledInstantAppState> uninstalledAppStates = 464 mUninstalledInstantApps.get(userId); 465 if (uninstalledAppStates == null) { 466 uninstalledAppStates = new ArrayList<>(); 467 mUninstalledInstantApps.put(userId, uninstalledAppStates); 468 } 469 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState( 470 uninstalledApp, System.currentTimeMillis()); 471 uninstalledAppStates.add(uninstalledAppState); 472 473 writeUninstalledInstantAppMetadata(uninstalledApp, userId); 474 writeInstantApplicationIconLPw(pkg, userId); 475 } 476 477 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg, 478 @UserIdInt int userId) { 479 File appDir = getInstantApplicationDir(pkg.packageName, userId); 480 if (!appDir.exists()) { 481 return; 482 } 483 484 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager()); 485 486 final Bitmap bitmap; 487 if (icon instanceof BitmapDrawable) { 488 bitmap = ((BitmapDrawable) icon).getBitmap(); 489 } else { 490 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), 491 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 492 Canvas canvas = new Canvas(bitmap); 493 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 494 icon.draw(canvas); 495 } 496 497 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId), 498 INSTANT_APP_ICON_FILE); 499 500 try (FileOutputStream out = new FileOutputStream(iconFile)) { 501 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 502 } catch (Exception e) { 503 Slog.e(LOG_TAG, "Error writing instant app icon", e); 504 } 505 } 506 507 boolean hasInstantApplicationMetadataLPr(String packageName, int userId) { 508 return hasUninstalledInstantAppStateLPr(packageName, userId) 509 || hasInstantAppMetadataLPr(packageName, userId); 510 } 511 512 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName, 513 @UserIdInt int userId) { 514 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> 515 state.mInstantAppInfo.getPackageName().equals(packageName), 516 userId); 517 518 File instantAppDir = getInstantApplicationDir(packageName, userId); 519 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete(); 520 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete(); 521 new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete(); 522 File cookie = peekInstantCookieFile(packageName, userId); 523 if (cookie != null) { 524 cookie.delete(); 525 } 526 } 527 528 private void removeUninstalledInstantAppStateLPw( 529 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) { 530 if (mUninstalledInstantApps == null) { 531 return; 532 } 533 List<UninstalledInstantAppState> uninstalledAppStates = 534 mUninstalledInstantApps.get(userId); 535 if (uninstalledAppStates == null) { 536 return; 537 } 538 final int appCount = uninstalledAppStates.size(); 539 for (int i = appCount - 1; i >= 0; --i) { 540 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 541 if (!criteria.test(uninstalledAppState)) { 542 continue; 543 } 544 uninstalledAppStates.remove(i); 545 if (uninstalledAppStates.isEmpty()) { 546 mUninstalledInstantApps.remove(userId); 547 if (mUninstalledInstantApps.size() <= 0) { 548 mUninstalledInstantApps = null; 549 } 550 return; 551 } 552 } 553 } 554 555 private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) { 556 if (mUninstalledInstantApps == null) { 557 return false; 558 } 559 final List<UninstalledInstantAppState> uninstalledAppStates = 560 mUninstalledInstantApps.get(userId); 561 if (uninstalledAppStates == null) { 562 return false; 563 } 564 final int appCount = uninstalledAppStates.size(); 565 for (int i = 0; i < appCount; i++) { 566 final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 567 if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) { 568 return true; 569 } 570 } 571 return false; 572 } 573 574 private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) { 575 final File instantAppDir = getInstantApplicationDir(packageName, userId); 576 return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists() 577 || new File(instantAppDir, INSTANT_APP_ICON_FILE).exists() 578 || new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).exists() 579 || peekInstantCookieFile(packageName, userId) != null; 580 } 581 582 void pruneInstantApps() { 583 final long maxInstalledCacheDuration = Settings.Global.getLong( 584 mService.mContext.getContentResolver(), 585 Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, 586 DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); 587 588 final long maxUninstalledCacheDuration = Settings.Global.getLong( 589 mService.mContext.getContentResolver(), 590 Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, 591 DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); 592 593 try { 594 pruneInstantApps(Long.MAX_VALUE, 595 maxInstalledCacheDuration, maxUninstalledCacheDuration); 596 } catch (IOException e) { 597 Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e); 598 } 599 } 600 601 boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) { 602 try { 603 return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE); 604 } catch (IOException e) { 605 Slog.e(LOG_TAG, "Error pruning installed instant apps", e); 606 return false; 607 } 608 } 609 610 boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) { 611 try { 612 return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration); 613 } catch (IOException e) { 614 Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e); 615 return false; 616 } 617 } 618 619 /** 620 * Prunes instant apps until there is enough <code>neededSpace</code>. Both 621 * installed and uninstalled instant apps are pruned that are older than 622 * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code> 623 * respectively. All times are in milliseconds. 624 * 625 * @param neededSpace The space to ensure is free. 626 * @param maxInstalledCacheDuration The max duration for caching installed apps in millis. 627 * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis. 628 * @return Whether enough space was freed. 629 * 630 * @throws IOException 631 */ 632 private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration, 633 long maxUninstalledCacheDuration) throws IOException { 634 final StorageManager storage = mService.mContext.getSystemService(StorageManager.class); 635 final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL); 636 637 if (file.getUsableSpace() >= neededSpace) { 638 return true; 639 } 640 641 List<String> packagesToDelete = null; 642 643 final int[] allUsers; 644 final long now = System.currentTimeMillis(); 645 646 // Prune first installed instant apps 647 synchronized (mService.mPackages) { 648 allUsers = PackageManagerService.sUserManager.getUserIds(); 649 650 final int packageCount = mService.mPackages.size(); 651 for (int i = 0; i < packageCount; i++) { 652 final PackageParser.Package pkg = mService.mPackages.valueAt(i); 653 if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) { 654 continue; 655 } 656 if (!(pkg.mExtras instanceof PackageSetting)) { 657 continue; 658 } 659 final PackageSetting ps = (PackageSetting) pkg.mExtras; 660 boolean installedOnlyAsInstantApp = false; 661 for (int userId : allUsers) { 662 if (ps.getInstalled(userId)) { 663 if (ps.getInstantApp(userId)) { 664 installedOnlyAsInstantApp = true; 665 } else { 666 installedOnlyAsInstantApp = false; 667 break; 668 } 669 } 670 } 671 if (installedOnlyAsInstantApp) { 672 if (packagesToDelete == null) { 673 packagesToDelete = new ArrayList<>(); 674 } 675 packagesToDelete.add(pkg.packageName); 676 } 677 } 678 679 if (packagesToDelete != null) { 680 packagesToDelete.sort((String lhs, String rhs) -> { 681 final PackageParser.Package lhsPkg = mService.mPackages.get(lhs); 682 final PackageParser.Package rhsPkg = mService.mPackages.get(rhs); 683 if (lhsPkg == null && rhsPkg == null) { 684 return 0; 685 } else if (lhsPkg == null) { 686 return -1; 687 } else if (rhsPkg == null) { 688 return 1; 689 } else { 690 if (lhsPkg.getLatestPackageUseTimeInMills() > 691 rhsPkg.getLatestPackageUseTimeInMills()) { 692 return 1; 693 } else if (lhsPkg.getLatestPackageUseTimeInMills() < 694 rhsPkg.getLatestPackageUseTimeInMills()) { 695 return -1; 696 } else { 697 if (lhsPkg.mExtras instanceof PackageSetting 698 && rhsPkg.mExtras instanceof PackageSetting) { 699 final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras; 700 final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras; 701 if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) { 702 return 1; 703 } else { 704 return -1; 705 } 706 } else { 707 return 0; 708 } 709 } 710 } 711 }); 712 } 713 } 714 715 if (packagesToDelete != null) { 716 final int packageCount = packagesToDelete.size(); 717 for (int i = 0; i < packageCount; i++) { 718 final String packageToDelete = packagesToDelete.get(i); 719 if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST, 720 UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS) 721 == PackageManager.DELETE_SUCCEEDED) { 722 if (file.getUsableSpace() >= neededSpace) { 723 return true; 724 } 725 } 726 } 727 } 728 729 // Prune uninstalled instant apps 730 synchronized (mService.mPackages) { 731 // TODO: Track last used time for uninstalled instant apps for better pruning 732 for (int userId : UserManagerService.getInstance().getUserIds()) { 733 // Prune in-memory state 734 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> { 735 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp; 736 return (elapsedCachingMillis > maxUninstalledCacheDuration); 737 }, userId); 738 739 // Prune on-disk state 740 File instantAppsDir = getInstantApplicationsDir(userId); 741 if (!instantAppsDir.exists()) { 742 continue; 743 } 744 File[] files = instantAppsDir.listFiles(); 745 if (files == null) { 746 continue; 747 } 748 for (File instantDir : files) { 749 if (!instantDir.isDirectory()) { 750 continue; 751 } 752 753 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE); 754 if (!metadataFile.exists()) { 755 continue; 756 } 757 758 final long elapsedCachingMillis = System.currentTimeMillis() 759 - metadataFile.lastModified(); 760 if (elapsedCachingMillis > maxUninstalledCacheDuration) { 761 deleteDir(instantDir); 762 if (file.getUsableSpace() >= neededSpace) { 763 return true; 764 } 765 } 766 } 767 } 768 } 769 770 return false; 771 } 772 773 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr( 774 @UserIdInt int userId) { 775 List<InstantAppInfo> result = null; 776 777 final int packageCount = mService.mPackages.size(); 778 for (int i = 0; i < packageCount; i++) { 779 final PackageParser.Package pkg = mService.mPackages.valueAt(i); 780 final PackageSetting ps = (PackageSetting) pkg.mExtras; 781 if (ps == null || !ps.getInstantApp(userId)) { 782 continue; 783 } 784 final InstantAppInfo info = createInstantAppInfoForPackage( 785 pkg, userId, true); 786 if (info == null) { 787 continue; 788 } 789 if (result == null) { 790 result = new ArrayList<>(); 791 } 792 result.add(info); 793 } 794 795 return result; 796 } 797 798 private @NonNull 799 InstantAppInfo createInstantAppInfoForPackage( 800 @NonNull PackageParser.Package pkg, @UserIdInt int userId, 801 boolean addApplicationInfo) { 802 PackageSetting ps = (PackageSetting) pkg.mExtras; 803 if (ps == null) { 804 return null; 805 } 806 if (!ps.getInstalled(userId)) { 807 return null; 808 } 809 810 String[] requestedPermissions = new String[pkg.requestedPermissions.size()]; 811 pkg.requestedPermissions.toArray(requestedPermissions); 812 813 Set<String> permissions = ps.getPermissionsState().getPermissions(userId); 814 String[] grantedPermissions = new String[permissions.size()]; 815 permissions.toArray(grantedPermissions); 816 817 if (addApplicationInfo) { 818 return new InstantAppInfo(pkg.applicationInfo, 819 requestedPermissions, grantedPermissions); 820 } else { 821 return new InstantAppInfo(pkg.applicationInfo.packageName, 822 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()), 823 requestedPermissions, grantedPermissions); 824 } 825 } 826 827 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr( 828 @UserIdInt int userId) { 829 List<UninstalledInstantAppState> uninstalledAppStates = 830 getUninstalledInstantAppStatesLPr(userId); 831 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) { 832 return null; 833 } 834 835 List<InstantAppInfo> uninstalledApps = null; 836 final int stateCount = uninstalledAppStates.size(); 837 for (int i = 0; i < stateCount; i++) { 838 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 839 if (uninstalledApps == null) { 840 uninstalledApps = new ArrayList<>(); 841 } 842 uninstalledApps.add(uninstalledAppState.mInstantAppInfo); 843 } 844 return uninstalledApps; 845 } 846 847 private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName, 848 @UserIdInt int userId) { 849 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo( 850 packageName, userId); 851 if (appInfo == null) { 852 return; 853 } 854 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) { 855 return; 856 } 857 final long identity = Binder.clearCallingIdentity(); 858 try { 859 for (String grantedPermission : appInfo.getGrantedPermissions()) { 860 BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission); 861 if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) { 862 mService.grantRuntimePermission(packageName, grantedPermission, userId); 863 } 864 } 865 } finally { 866 Binder.restoreCallingIdentity(identity); 867 } 868 } 869 870 private @NonNull 871 InstantAppInfo peekOrParseUninstalledInstantAppInfo( 872 @NonNull String packageName, @UserIdInt int userId) { 873 if (mUninstalledInstantApps != null) { 874 List<UninstalledInstantAppState> uninstalledAppStates = 875 mUninstalledInstantApps.get(userId); 876 if (uninstalledAppStates != null) { 877 final int appCount = uninstalledAppStates.size(); 878 for (int i = 0; i < appCount; i++) { 879 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 880 if (uninstalledAppState.mInstantAppInfo 881 .getPackageName().equals(packageName)) { 882 return uninstalledAppState.mInstantAppInfo; 883 } 884 } 885 } 886 } 887 888 File metadataFile = new File(getInstantApplicationDir(packageName, userId), 889 INSTANT_APP_METADATA_FILE); 890 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile); 891 if (uninstalledAppState == null) { 892 return null; 893 } 894 895 return uninstalledAppState.mInstantAppInfo; 896 } 897 898 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr( 899 @UserIdInt int userId) { 900 List<UninstalledInstantAppState> uninstalledAppStates = null; 901 if (mUninstalledInstantApps != null) { 902 uninstalledAppStates = mUninstalledInstantApps.get(userId); 903 if (uninstalledAppStates != null) { 904 return uninstalledAppStates; 905 } 906 } 907 908 File instantAppsDir = getInstantApplicationsDir(userId); 909 if (instantAppsDir.exists()) { 910 File[] files = instantAppsDir.listFiles(); 911 if (files != null) { 912 for (File instantDir : files) { 913 if (!instantDir.isDirectory()) { 914 continue; 915 } 916 File metadataFile = new File(instantDir, 917 INSTANT_APP_METADATA_FILE); 918 UninstalledInstantAppState uninstalledAppState = 919 parseMetadataFile(metadataFile); 920 if (uninstalledAppState == null) { 921 continue; 922 } 923 if (uninstalledAppStates == null) { 924 uninstalledAppStates = new ArrayList<>(); 925 } 926 uninstalledAppStates.add(uninstalledAppState); 927 } 928 } 929 } 930 931 if (uninstalledAppStates != null) { 932 if (mUninstalledInstantApps == null) { 933 mUninstalledInstantApps = new SparseArray<>(); 934 } 935 mUninstalledInstantApps.put(userId, uninstalledAppStates); 936 } 937 938 return uninstalledAppStates; 939 } 940 941 private static @Nullable UninstalledInstantAppState parseMetadataFile( 942 @NonNull File metadataFile) { 943 if (!metadataFile.exists()) { 944 return null; 945 } 946 FileInputStream in; 947 try { 948 in = new AtomicFile(metadataFile).openRead(); 949 } catch (FileNotFoundException fnfe) { 950 Slog.i(LOG_TAG, "No instant metadata file"); 951 return null; 952 } 953 954 final File instantDir = metadataFile.getParentFile(); 955 final long timestamp = metadataFile.lastModified(); 956 final String packageName = instantDir.getName(); 957 958 try { 959 XmlPullParser parser = Xml.newPullParser(); 960 parser.setInput(in, StandardCharsets.UTF_8.name()); 961 return new UninstalledInstantAppState( 962 parseMetadata(parser, packageName), timestamp); 963 } catch (XmlPullParserException | IOException e) { 964 throw new IllegalStateException("Failed parsing instant" 965 + " metadata file: " + metadataFile, e); 966 } finally { 967 IoUtils.closeQuietly(in); 968 } 969 } 970 971 private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg, 972 @UserIdInt int userId) { 973 File appDir = getInstantApplicationDir(pkg.packageName, userId); 974 String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest( 975 pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX; 976 return new File(appDir, cookieFile); 977 } 978 979 private static @Nullable File peekInstantCookieFile(@NonNull String packageName, 980 @UserIdInt int userId) { 981 File appDir = getInstantApplicationDir(packageName, userId); 982 if (!appDir.exists()) { 983 return null; 984 } 985 File[] files = appDir.listFiles(); 986 if (files == null) { 987 return null; 988 } 989 for (File file : files) { 990 if (!file.isDirectory() 991 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX) 992 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) { 993 return file; 994 } 995 } 996 return null; 997 } 998 999 private static @Nullable 1000 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser, 1001 @NonNull String packageName) 1002 throws IOException, XmlPullParserException { 1003 final int outerDepth = parser.getDepth(); 1004 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1005 if (TAG_PACKAGE.equals(parser.getName())) { 1006 return parsePackage(parser, packageName); 1007 } 1008 } 1009 return null; 1010 } 1011 1012 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser, 1013 @NonNull String packageName) 1014 throws IOException, XmlPullParserException { 1015 String label = parser.getAttributeValue(null, ATTR_LABEL); 1016 1017 List<String> outRequestedPermissions = new ArrayList<>(); 1018 List<String> outGrantedPermissions = new ArrayList<>(); 1019 1020 final int outerDepth = parser.getDepth(); 1021 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1022 if (TAG_PERMISSIONS.equals(parser.getName())) { 1023 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions); 1024 } 1025 } 1026 1027 String[] requestedPermissions = new String[outRequestedPermissions.size()]; 1028 outRequestedPermissions.toArray(requestedPermissions); 1029 1030 String[] grantedPermissions = new String[outGrantedPermissions.size()]; 1031 outGrantedPermissions.toArray(grantedPermissions); 1032 1033 return new InstantAppInfo(packageName, label, 1034 requestedPermissions, grantedPermissions); 1035 } 1036 1037 private static void parsePermissions(@NonNull XmlPullParser parser, 1038 @NonNull List<String> outRequestedPermissions, 1039 @NonNull List<String> outGrantedPermissions) 1040 throws IOException, XmlPullParserException { 1041 final int outerDepth = parser.getDepth(); 1042 while (XmlUtils.nextElementWithin(parser,outerDepth)) { 1043 if (TAG_PERMISSION.equals(parser.getName())) { 1044 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME); 1045 outRequestedPermissions.add(permission); 1046 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) { 1047 outGrantedPermissions.add(permission); 1048 } 1049 } 1050 } 1051 } 1052 1053 private void writeUninstalledInstantAppMetadata( 1054 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) { 1055 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId); 1056 if (!appDir.exists() && !appDir.mkdirs()) { 1057 return; 1058 } 1059 1060 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE); 1061 1062 AtomicFile destination = new AtomicFile(metadataFile); 1063 FileOutputStream out = null; 1064 try { 1065 out = destination.startWrite(); 1066 1067 XmlSerializer serializer = Xml.newSerializer(); 1068 serializer.setOutput(out, StandardCharsets.UTF_8.name()); 1069 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 1070 1071 serializer.startDocument(null, true); 1072 1073 serializer.startTag(null, TAG_PACKAGE); 1074 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel( 1075 mService.mContext.getPackageManager()).toString()); 1076 1077 serializer.startTag(null, TAG_PERMISSIONS); 1078 for (String permission : instantApp.getRequestedPermissions()) { 1079 serializer.startTag(null, TAG_PERMISSION); 1080 serializer.attribute(null, ATTR_NAME, permission); 1081 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) { 1082 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true)); 1083 } 1084 serializer.endTag(null, TAG_PERMISSION); 1085 } 1086 serializer.endTag(null, TAG_PERMISSIONS); 1087 1088 serializer.endTag(null, TAG_PACKAGE); 1089 1090 serializer.endDocument(); 1091 destination.finishWrite(out); 1092 } catch (Throwable t) { 1093 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t); 1094 destination.failWrite(out); 1095 } finally { 1096 IoUtils.closeQuietly(out); 1097 } 1098 } 1099 1100 private static @NonNull File getInstantApplicationsDir(int userId) { 1101 return new File(Environment.getUserSystemDirectory(userId), 1102 INSTANT_APPS_FOLDER); 1103 } 1104 1105 private static @NonNull File getInstantApplicationDir(String packageName, int userId) { 1106 return new File(getInstantApplicationsDir(userId), packageName); 1107 } 1108 1109 private static void deleteDir(@NonNull File dir) { 1110 File[] files = dir.listFiles(); 1111 if (files != null) { 1112 for (File file : files) { 1113 deleteDir(file); 1114 } 1115 } 1116 dir.delete(); 1117 } 1118 1119 private static final class UninstalledInstantAppState { 1120 final InstantAppInfo mInstantAppInfo; 1121 final long mTimestamp; 1122 1123 public UninstalledInstantAppState(InstantAppInfo instantApp, 1124 long timestamp) { 1125 mInstantAppInfo = instantApp; 1126 mTimestamp = timestamp; 1127 } 1128 } 1129 1130 private final class CookiePersistence extends Handler { 1131 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */ 1132 1133 // In case you wonder why we stash the cookies aside, we use 1134 // the user id for the message id and the package for the payload. 1135 // Handler allows removing messages by id and tag where the 1136 // tag is compared using ==. So to allow cancelling the 1137 // pending persistence for an app under a given user we use 1138 // the fact that package are cached by the system so the == 1139 // comparison would match and we end up with a way to cancel 1140 // persisting the cookie for a user and package. 1141 private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies 1142 = new SparseArray<>(); 1143 1144 public CookiePersistence(Looper looper) { 1145 super(looper); 1146 } 1147 1148 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg, 1149 @NonNull byte[] cookie) { 1150 File cookieFile = computeInstantCookieFile(pkg, userId); 1151 cancelPendingPersistLPw(pkg, userId); 1152 addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile); 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