InstantAppRegistry.java revision be0b8896d1bc385d4c8fb54c21929745935dcbea
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.PackageParser; 25import android.graphics.Bitmap; 26import android.graphics.BitmapFactory; 27import android.graphics.Canvas; 28import android.graphics.drawable.BitmapDrawable; 29import android.graphics.drawable.Drawable; 30import android.os.Binder; 31import android.os.Environment; 32import android.os.Handler; 33import android.os.Looper; 34import android.os.Message; 35import android.provider.Settings; 36import android.util.ArrayMap; 37import android.util.AtomicFile; 38import android.util.PackageUtils; 39import android.util.Slog; 40import android.util.SparseArray; 41import android.util.SparseBooleanArray; 42import android.util.Xml; 43import com.android.internal.annotations.GuardedBy; 44import com.android.internal.os.BackgroundThread; 45import com.android.internal.util.ArrayUtils; 46import com.android.internal.util.XmlUtils; 47import libcore.io.IoUtils; 48import org.xmlpull.v1.XmlPullParser; 49import org.xmlpull.v1.XmlPullParserException; 50import org.xmlpull.v1.XmlSerializer; 51 52import java.io.File; 53import java.io.FileInputStream; 54import java.io.FileNotFoundException; 55import java.io.FileOutputStream; 56import java.io.IOException; 57import java.nio.charset.StandardCharsets; 58import java.util.ArrayList; 59import java.util.List; 60import java.util.Set; 61import java.util.function.Predicate; 62 63/** 64 * This class is a part of the package manager service that is responsible 65 * for managing data associated with instant apps such as cached uninstalled 66 * instant apps and instant apps' cookies. In addition it is responsible for 67 * pruning installed instant apps and meta-data for uninstalled instant apps 68 * when free space is needed. 69 */ 70class InstantAppRegistry { 71 private static final boolean DEBUG = false; 72 73 private static final String LOG_TAG = "InstantAppRegistry"; 74 75 private static final long DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS = 76 DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */ 77 78 private static final String INSTANT_APPS_FOLDER = "instant"; 79 private static final String INSTANT_APP_ICON_FILE = "icon.png"; 80 private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_"; 81 private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat"; 82 private static final String INSTANT_APP_METADATA_FILE = "metadata.xml"; 83 84 private static final String TAG_PACKAGE = "package"; 85 private static final String TAG_PERMISSIONS = "permissions"; 86 private static final String TAG_PERMISSION = "permission"; 87 88 private static final String ATTR_LABEL = "label"; 89 private static final String ATTR_NAME = "name"; 90 private static final String ATTR_GRANTED = "granted"; 91 92 private final PackageManagerService mService; 93 private final CookiePersistence mCookiePersistence; 94 95 /** State for uninstalled instant apps */ 96 @GuardedBy("mService.mPackages") 97 private SparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps; 98 99 /** 100 * Automatic grants for access to instant app metadata. 101 * The key is the target application UID. 102 * The value is a set of instant app UIDs. 103 * UserID -> TargetAppId -> InstantAppId 104 */ 105 @GuardedBy("mService.mPackages") 106 private SparseArray<SparseArray<SparseBooleanArray>> mInstantGrants; 107 108 /** The set of all installed instant apps. UserID -> AppID */ 109 @GuardedBy("mService.mPackages") 110 private SparseArray<SparseBooleanArray> mInstalledInstantAppUids; 111 112 public InstantAppRegistry(PackageManagerService service) { 113 mService = service; 114 mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper()); 115 } 116 117 public byte[] getInstantAppCookieLPw(@NonNull String packageName, 118 @UserIdInt int userId) { 119 byte[] pendingCookie = mCookiePersistence.getPendingPersistCookie(userId, packageName); 120 if (pendingCookie != null) { 121 return pendingCookie; 122 } 123 File cookieFile = peekInstantCookieFile(packageName, userId); 124 if (cookieFile != null && cookieFile.exists()) { 125 try { 126 return IoUtils.readFileAsByteArray(cookieFile.toString()); 127 } catch (IOException e) { 128 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile); 129 } 130 } 131 return null; 132 } 133 134 public boolean setInstantAppCookieLPw(@NonNull String packageName, 135 @Nullable byte[] cookie, @UserIdInt int userId) { 136 if (cookie != null && cookie.length > 0) { 137 final int maxCookieSize = mService.mContext.getPackageManager() 138 .getInstantAppCookieMaxSize(); 139 if (cookie.length > maxCookieSize) { 140 Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size " 141 + cookie.length + " bytes while max size is " + maxCookieSize); 142 return false; 143 } 144 } 145 146 mCookiePersistence.schedulePersist(userId, packageName, cookie); 147 return true; 148 } 149 150 private void persistInstantApplicationCookie(@Nullable byte[] cookie, 151 @NonNull String packageName, @UserIdInt int userId) { 152 synchronized (mService.mPackages) { 153 PackageParser.Package pkg = mService.mPackages.get(packageName); 154 if (pkg == null) { 155 return; 156 } 157 158 File appDir = getInstantApplicationDir(packageName, userId); 159 if (!appDir.exists() && !appDir.mkdirs()) { 160 Slog.e(LOG_TAG, "Cannot create instant app cookie directory"); 161 return; 162 } 163 164 File cookieFile = computeInstantCookieFile(pkg, userId); 165 if (cookieFile.exists() && !cookieFile.delete()) { 166 Slog.e(LOG_TAG, "Cannot delete instant app cookie file"); 167 } 168 169 // No cookie or an empty one means delete - done 170 if (cookie == null || cookie.length <= 0) { 171 return; 172 } 173 174 try (FileOutputStream fos = new FileOutputStream(cookieFile)) { 175 fos.write(cookie, 0, cookie.length); 176 } catch (IOException e) { 177 Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e); 178 } 179 } 180 } 181 182 public Bitmap getInstantAppIconLPw(@NonNull String packageName, 183 @UserIdInt int userId) { 184 File iconFile = new File(getInstantApplicationDir(packageName, userId), 185 INSTANT_APP_ICON_FILE); 186 if (iconFile.exists()) { 187 return BitmapFactory.decodeFile(iconFile.toString()); 188 } 189 return null; 190 } 191 192 public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) { 193 List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId); 194 List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId); 195 if (installedApps != null) { 196 if (uninstalledApps != null) { 197 installedApps.addAll(uninstalledApps); 198 } 199 return installedApps; 200 } 201 return uninstalledApps; 202 } 203 204 public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) { 205 PackageSetting ps = (PackageSetting) pkg.mExtras; 206 if (ps == null) { 207 return; 208 } 209 210 for (int userId : userIds) { 211 // Ignore not installed apps 212 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) { 213 continue; 214 } 215 216 // Propagate permissions before removing any state 217 propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId); 218 219 // Track instant apps 220 if (ps.getInstantApp(userId)) { 221 addInstantAppLPw(userId, ps.appId); 222 } 223 224 // Remove the in-memory state 225 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> 226 state.mInstantAppInfo.getPackageName().equals(pkg.packageName), 227 userId); 228 229 // Remove the on-disk state except the cookie 230 File instantAppDir = getInstantApplicationDir(pkg.packageName, userId); 231 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete(); 232 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete(); 233 234 // If app signature changed - wipe the cookie 235 File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId); 236 if (currentCookieFile == null) { 237 continue; 238 } 239 File expectedCookeFile = computeInstantCookieFile(pkg, userId); 240 if (!currentCookieFile.equals(expectedCookeFile)) { 241 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName 242 + " changed - dropping cookie"); 243 currentCookieFile.delete(); 244 } 245 } 246 } 247 248 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg, 249 @NonNull int[] userIds) { 250 PackageSetting ps = (PackageSetting) pkg.mExtras; 251 if (ps == null) { 252 return; 253 } 254 255 for (int userId : userIds) { 256 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) { 257 continue; 258 } 259 260 if (ps.getInstantApp(userId)) { 261 // Add a record for an uninstalled instant app 262 addUninstalledInstantAppLPw(pkg, userId); 263 removeInstantAppLPw(userId, ps.appId); 264 } else { 265 // Deleting an app prunes all instant state such as cookie 266 deleteDir(getInstantApplicationDir(pkg.packageName, userId)); 267 removeAppLPw(userId, ps.appId); 268 } 269 } 270 } 271 272 public void onUserRemovedLPw(int userId) { 273 if (mUninstalledInstantApps != null) { 274 mUninstalledInstantApps.remove(userId); 275 if (mUninstalledInstantApps.size() <= 0) { 276 mUninstalledInstantApps = null; 277 } 278 } 279 if (mInstalledInstantAppUids != null) { 280 mInstalledInstantAppUids.remove(userId); 281 if (mInstalledInstantAppUids.size() <= 0) { 282 mInstalledInstantAppUids = null; 283 } 284 } 285 if (mInstantGrants != null) { 286 mInstantGrants.remove(userId); 287 if (mInstantGrants.size() <= 0) { 288 mInstantGrants = null; 289 } 290 } 291 deleteDir(getInstantApplicationsDir(userId)); 292 } 293 294 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId, 295 int instantAppId) { 296 if (mInstantGrants == null) { 297 return false; 298 } 299 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 300 if (targetAppList == null) { 301 return false; 302 } 303 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId); 304 if (instantGrantList == null) { 305 return false; 306 } 307 return instantGrantList.get(instantAppId); 308 } 309 310 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent, 311 int targetAppId, int instantAppId) { 312 if (mInstalledInstantAppUids == null) { 313 return; // no instant apps installed; no need to grant 314 } 315 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 316 if (instantAppList == null || !instantAppList.get(instantAppId)) { 317 return; // instant app id isn't installed; no need to grant 318 } 319 if (instantAppList.get(targetAppId)) { 320 return; // target app id is an instant app; no need to grant 321 } 322 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { 323 final Set<String> categories = intent.getCategories(); 324 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) { 325 return; // launched via VIEW/BROWSABLE intent; no need to grant 326 } 327 } 328 if (mInstantGrants == null) { 329 mInstantGrants = new SparseArray<>(); 330 } 331 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 332 if (targetAppList == null) { 333 targetAppList = new SparseArray<>(); 334 mInstantGrants.put(userId, targetAppList); 335 } 336 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId); 337 if (instantGrantList == null) { 338 instantGrantList = new SparseBooleanArray(); 339 targetAppList.put(targetAppId, instantGrantList); 340 } 341 instantGrantList.put(instantAppId, true /*granted*/); 342 } 343 344 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) { 345 if (mInstalledInstantAppUids == null) { 346 mInstalledInstantAppUids = new SparseArray<>(); 347 } 348 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 349 if (instantAppList == null) { 350 instantAppList = new SparseBooleanArray(); 351 mInstalledInstantAppUids.put(userId, instantAppList); 352 } 353 instantAppList.put(instantAppId, true /*installed*/); 354 } 355 356 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) { 357 // remove from the installed list 358 if (mInstalledInstantAppUids == null) { 359 return; // no instant apps on the system 360 } 361 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 362 if (instantAppList == null) { 363 return; 364 } 365 366 instantAppList.delete(instantAppId); 367 368 // remove any grants 369 if (mInstantGrants == null) { 370 return; // no grants on the system 371 } 372 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 373 if (targetAppList == null) { 374 return; // no grants for this user 375 } 376 for (int i = targetAppList.size() - 1; i >= 0; --i) { 377 targetAppList.valueAt(i).delete(instantAppId); 378 } 379 } 380 381 private void removeAppLPw(@UserIdInt int userId, int targetAppId) { 382 // remove from the installed list 383 if (mInstantGrants == null) { 384 return; // no grants on the system 385 } 386 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 387 if (targetAppList == null) { 388 return; // no grants for this user 389 } 390 targetAppList.delete(targetAppId); 391 } 392 393 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg, 394 @UserIdInt int userId) { 395 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage( 396 pkg, userId, false); 397 if (uninstalledApp == null) { 398 return; 399 } 400 if (mUninstalledInstantApps == null) { 401 mUninstalledInstantApps = new SparseArray<>(); 402 } 403 List<UninstalledInstantAppState> uninstalledAppStates = 404 mUninstalledInstantApps.get(userId); 405 if (uninstalledAppStates == null) { 406 uninstalledAppStates = new ArrayList<>(); 407 mUninstalledInstantApps.put(userId, uninstalledAppStates); 408 } 409 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState( 410 uninstalledApp, System.currentTimeMillis()); 411 uninstalledAppStates.add(uninstalledAppState); 412 413 writeUninstalledInstantAppMetadata(uninstalledApp, userId); 414 writeInstantApplicationIconLPw(pkg, userId); 415 } 416 417 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg, 418 @UserIdInt int userId) { 419 File appDir = getInstantApplicationDir(pkg.packageName, userId); 420 if (!appDir.exists()) { 421 return; 422 } 423 424 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager()); 425 426 final Bitmap bitmap; 427 if (icon instanceof BitmapDrawable) { 428 bitmap = ((BitmapDrawable) icon).getBitmap(); 429 } else { 430 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), 431 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 432 Canvas canvas = new Canvas(bitmap); 433 icon.draw(canvas); 434 } 435 436 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId), 437 INSTANT_APP_ICON_FILE); 438 439 try (FileOutputStream out = new FileOutputStream(iconFile)) { 440 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 441 } catch (Exception e) { 442 Slog.e(LOG_TAG, "Error writing instant app icon", e); 443 } 444 } 445 446 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName, 447 @UserIdInt int userId) { 448 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> 449 state.mInstantAppInfo.getPackageName().equals(packageName), 450 userId); 451 452 File instantAppDir = getInstantApplicationDir(packageName, userId); 453 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete(); 454 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete(); 455 File cookie = peekInstantCookieFile(packageName, userId); 456 if (cookie != null) { 457 cookie.delete(); 458 } 459 } 460 461 private void removeUninstalledInstantAppStateLPw( 462 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) { 463 if (mUninstalledInstantApps == null) { 464 return; 465 } 466 List<UninstalledInstantAppState> uninstalledAppStates = 467 mUninstalledInstantApps.get(userId); 468 if (uninstalledAppStates == null) { 469 return; 470 } 471 final int appCount = uninstalledAppStates.size(); 472 for (int i = appCount - 1; i >= 0; --i) { 473 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 474 if (!criteria.test(uninstalledAppState)) { 475 continue; 476 } 477 uninstalledAppStates.remove(i); 478 if (uninstalledAppStates.isEmpty()) { 479 mUninstalledInstantApps.remove(userId); 480 if (mUninstalledInstantApps.size() <= 0) { 481 mUninstalledInstantApps = null; 482 } 483 return; 484 } 485 } 486 } 487 488 public void pruneInstantAppsLPw() { 489 // For now we prune only state for uninstalled instant apps 490 final long maxCacheDurationMillis = Settings.Global.getLong( 491 mService.mContext.getContentResolver(), 492 Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS, 493 DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS); 494 495 for (int userId : UserManagerService.getInstance().getUserIds()) { 496 // Prune in-memory state 497 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> { 498 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp; 499 return (elapsedCachingMillis > maxCacheDurationMillis); 500 }, userId); 501 502 // Prune on-disk state 503 File instantAppsDir = getInstantApplicationsDir(userId); 504 if (!instantAppsDir.exists()) { 505 continue; 506 } 507 File[] files = instantAppsDir.listFiles(); 508 if (files == null) { 509 continue; 510 } 511 for (File instantDir : files) { 512 if (!instantDir.isDirectory()) { 513 continue; 514 } 515 516 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE); 517 if (!metadataFile.exists()) { 518 continue; 519 } 520 521 final long elapsedCachingMillis = System.currentTimeMillis() 522 - metadataFile.lastModified(); 523 if (elapsedCachingMillis > maxCacheDurationMillis) { 524 deleteDir(instantDir); 525 } 526 } 527 } 528 } 529 530 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr( 531 @UserIdInt int userId) { 532 List<InstantAppInfo> result = null; 533 534 final int packageCount = mService.mPackages.size(); 535 for (int i = 0; i < packageCount; i++) { 536 final PackageParser.Package pkg = mService.mPackages.valueAt(i); 537 final PackageSetting ps = (PackageSetting) pkg.mExtras; 538 if (ps == null || !ps.getInstantApp(userId)) { 539 continue; 540 } 541 final InstantAppInfo info = createInstantAppInfoForPackage( 542 pkg, userId, true); 543 if (info == null) { 544 continue; 545 } 546 if (result == null) { 547 result = new ArrayList<>(); 548 } 549 result.add(info); 550 } 551 552 return result; 553 } 554 555 private @NonNull 556 InstantAppInfo createInstantAppInfoForPackage( 557 @NonNull PackageParser.Package pkg, @UserIdInt int userId, 558 boolean addApplicationInfo) { 559 PackageSetting ps = (PackageSetting) pkg.mExtras; 560 if (ps == null) { 561 return null; 562 } 563 if (!ps.getInstalled(userId)) { 564 return null; 565 } 566 567 String[] requestedPermissions = new String[pkg.requestedPermissions.size()]; 568 pkg.requestedPermissions.toArray(requestedPermissions); 569 570 Set<String> permissions = ps.getPermissionsState().getPermissions(userId); 571 String[] grantedPermissions = new String[permissions.size()]; 572 permissions.toArray(grantedPermissions); 573 574 if (addApplicationInfo) { 575 return new InstantAppInfo(pkg.applicationInfo, 576 requestedPermissions, grantedPermissions); 577 } else { 578 return new InstantAppInfo(pkg.applicationInfo.packageName, 579 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()), 580 requestedPermissions, grantedPermissions); 581 } 582 } 583 584 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr( 585 @UserIdInt int userId) { 586 List<UninstalledInstantAppState> uninstalledAppStates = 587 getUninstalledInstantAppStatesLPr(userId); 588 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) { 589 return null; 590 } 591 592 List<InstantAppInfo> uninstalledApps = null; 593 final int stateCount = uninstalledAppStates.size(); 594 for (int i = 0; i < stateCount; i++) { 595 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 596 if (uninstalledApps == null) { 597 uninstalledApps = new ArrayList<>(); 598 } 599 uninstalledApps.add(uninstalledAppState.mInstantAppInfo); 600 } 601 return uninstalledApps; 602 } 603 604 private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName, 605 @UserIdInt int userId) { 606 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo( 607 packageName, userId); 608 if (appInfo == null) { 609 return; 610 } 611 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) { 612 return; 613 } 614 final long identity = Binder.clearCallingIdentity(); 615 try { 616 for (String grantedPermission : appInfo.getGrantedPermissions()) { 617 BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission); 618 if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) { 619 mService.grantRuntimePermission(packageName, grantedPermission, userId); 620 } 621 } 622 } finally { 623 Binder.restoreCallingIdentity(identity); 624 } 625 } 626 627 private @NonNull 628 InstantAppInfo peekOrParseUninstalledInstantAppInfo( 629 @NonNull String packageName, @UserIdInt int userId) { 630 if (mUninstalledInstantApps != null) { 631 List<UninstalledInstantAppState> uninstalledAppStates = 632 mUninstalledInstantApps.get(userId); 633 if (uninstalledAppStates != null) { 634 final int appCount = uninstalledAppStates.size(); 635 for (int i = 0; i < appCount; i++) { 636 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 637 if (uninstalledAppState.mInstantAppInfo 638 .getPackageName().equals(packageName)) { 639 return uninstalledAppState.mInstantAppInfo; 640 } 641 } 642 } 643 } 644 645 File metadataFile = new File(getInstantApplicationDir(packageName, userId), 646 INSTANT_APP_METADATA_FILE); 647 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile); 648 if (uninstalledAppState == null) { 649 return null; 650 } 651 652 return uninstalledAppState.mInstantAppInfo; 653 } 654 655 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr( 656 @UserIdInt int userId) { 657 List<UninstalledInstantAppState> uninstalledAppStates = null; 658 if (mUninstalledInstantApps != null) { 659 uninstalledAppStates = mUninstalledInstantApps.get(userId); 660 if (uninstalledAppStates != null) { 661 return uninstalledAppStates; 662 } 663 } 664 665 File instantAppsDir = getInstantApplicationsDir(userId); 666 if (instantAppsDir.exists()) { 667 File[] files = instantAppsDir.listFiles(); 668 if (files != null) { 669 for (File instantDir : files) { 670 if (!instantDir.isDirectory()) { 671 continue; 672 } 673 File metadataFile = new File(instantDir, 674 INSTANT_APP_METADATA_FILE); 675 UninstalledInstantAppState uninstalledAppState = 676 parseMetadataFile(metadataFile); 677 if (uninstalledAppState == null) { 678 continue; 679 } 680 if (uninstalledAppStates == null) { 681 uninstalledAppStates = new ArrayList<>(); 682 } 683 uninstalledAppStates.add(uninstalledAppState); 684 } 685 } 686 } 687 688 if (uninstalledAppStates != null) { 689 if (mUninstalledInstantApps == null) { 690 mUninstalledInstantApps = new SparseArray<>(); 691 } 692 mUninstalledInstantApps.put(userId, uninstalledAppStates); 693 } 694 695 return uninstalledAppStates; 696 } 697 698 private static @Nullable UninstalledInstantAppState parseMetadataFile( 699 @NonNull File metadataFile) { 700 if (!metadataFile.exists()) { 701 return null; 702 } 703 FileInputStream in; 704 try { 705 in = new AtomicFile(metadataFile).openRead(); 706 } catch (FileNotFoundException fnfe) { 707 Slog.i(LOG_TAG, "No instant metadata file"); 708 return null; 709 } 710 711 final File instantDir = metadataFile.getParentFile(); 712 final long timestamp = metadataFile.lastModified(); 713 final String packageName = instantDir.getName(); 714 715 try { 716 XmlPullParser parser = Xml.newPullParser(); 717 parser.setInput(in, StandardCharsets.UTF_8.name()); 718 return new UninstalledInstantAppState( 719 parseMetadata(parser, packageName), timestamp); 720 } catch (XmlPullParserException | IOException e) { 721 throw new IllegalStateException("Failed parsing instant" 722 + " metadata file: " + metadataFile, e); 723 } finally { 724 IoUtils.closeQuietly(in); 725 } 726 } 727 728 private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg, 729 @UserIdInt int userId) { 730 File appDir = getInstantApplicationDir(pkg.packageName, userId); 731 String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest( 732 pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX; 733 return new File(appDir, cookieFile); 734 } 735 736 private static @Nullable File peekInstantCookieFile(@NonNull String packageName, 737 @UserIdInt int userId) { 738 File appDir = getInstantApplicationDir(packageName, userId); 739 if (!appDir.exists()) { 740 return null; 741 } 742 File[] files = appDir.listFiles(); 743 if (files == null) { 744 return null; 745 } 746 for (File file : files) { 747 if (!file.isDirectory() 748 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX) 749 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) { 750 return file; 751 } 752 } 753 return null; 754 } 755 756 private static @Nullable 757 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser, 758 @NonNull String packageName) 759 throws IOException, XmlPullParserException { 760 final int outerDepth = parser.getDepth(); 761 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 762 if (TAG_PACKAGE.equals(parser.getName())) { 763 return parsePackage(parser, packageName); 764 } 765 } 766 return null; 767 } 768 769 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser, 770 @NonNull String packageName) 771 throws IOException, XmlPullParserException { 772 String label = parser.getAttributeValue(null, ATTR_LABEL); 773 774 List<String> outRequestedPermissions = new ArrayList<>(); 775 List<String> outGrantedPermissions = new ArrayList<>(); 776 777 final int outerDepth = parser.getDepth(); 778 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 779 if (TAG_PERMISSIONS.equals(parser.getName())) { 780 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions); 781 } 782 } 783 784 String[] requestedPermissions = new String[outRequestedPermissions.size()]; 785 outRequestedPermissions.toArray(requestedPermissions); 786 787 String[] grantedPermissions = new String[outGrantedPermissions.size()]; 788 outGrantedPermissions.toArray(grantedPermissions); 789 790 return new InstantAppInfo(packageName, label, 791 requestedPermissions, grantedPermissions); 792 } 793 794 private static void parsePermissions(@NonNull XmlPullParser parser, 795 @NonNull List<String> outRequestedPermissions, 796 @NonNull List<String> outGrantedPermissions) 797 throws IOException, XmlPullParserException { 798 final int outerDepth = parser.getDepth(); 799 while (XmlUtils.nextElementWithin(parser,outerDepth)) { 800 if (TAG_PERMISSION.equals(parser.getName())) { 801 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME); 802 outRequestedPermissions.add(permission); 803 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) { 804 outGrantedPermissions.add(permission); 805 } 806 } 807 } 808 } 809 810 private void writeUninstalledInstantAppMetadata( 811 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) { 812 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId); 813 if (!appDir.exists() && !appDir.mkdirs()) { 814 return; 815 } 816 817 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE); 818 819 AtomicFile destination = new AtomicFile(metadataFile); 820 FileOutputStream out = null; 821 try { 822 out = destination.startWrite(); 823 824 XmlSerializer serializer = Xml.newSerializer(); 825 serializer.setOutput(out, StandardCharsets.UTF_8.name()); 826 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 827 828 serializer.startDocument(null, true); 829 830 serializer.startTag(null, TAG_PACKAGE); 831 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel( 832 mService.mContext.getPackageManager()).toString()); 833 834 serializer.startTag(null, TAG_PERMISSIONS); 835 for (String permission : instantApp.getRequestedPermissions()) { 836 serializer.startTag(null, TAG_PERMISSION); 837 serializer.attribute(null, ATTR_NAME, permission); 838 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) { 839 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true)); 840 } 841 serializer.endTag(null, TAG_PERMISSION); 842 } 843 serializer.endTag(null, TAG_PERMISSIONS); 844 845 serializer.endTag(null, TAG_PACKAGE); 846 847 serializer.endDocument(); 848 destination.finishWrite(out); 849 } catch (Throwable t) { 850 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t); 851 destination.failWrite(out); 852 } finally { 853 IoUtils.closeQuietly(out); 854 } 855 } 856 857 private static @NonNull File getInstantApplicationsDir(int userId) { 858 return new File(Environment.getUserSystemDirectory(userId), 859 INSTANT_APPS_FOLDER); 860 } 861 862 private static @NonNull File getInstantApplicationDir(String packageName, int userId) { 863 return new File (getInstantApplicationsDir(userId), packageName); 864 } 865 866 private static void deleteDir(@NonNull File dir) { 867 File[] files = dir.listFiles(); 868 if (files != null) { 869 for (File file : files) { 870 deleteDir(file); 871 } 872 } 873 dir.delete(); 874 } 875 876 private static final class UninstalledInstantAppState { 877 final InstantAppInfo mInstantAppInfo; 878 final long mTimestamp; 879 880 public UninstalledInstantAppState(InstantAppInfo instantApp, 881 long timestamp) { 882 mInstantAppInfo = instantApp; 883 mTimestamp = timestamp; 884 } 885 } 886 887 private final class CookiePersistence extends Handler { 888 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */ 889 890 // In case you wonder why we stash the cookies aside, we use 891 // the user id for the message id and the package for the payload. 892 // Handler allows removing messages by id and tag where the 893 // tag is is compared using ==. So to allow cancelling the 894 // pending persistence for an app under a given user we use 895 // the fact that package names are interned in the system 896 // process so the == comparison would match and we end up 897 // with a way to cancel persisting the cookie for a user 898 // and package. 899 private final SparseArray<ArrayMap<String, byte[]>> mPendingPersistCookies = 900 new SparseArray<>(); 901 902 public CookiePersistence(Looper looper) { 903 super(looper); 904 } 905 906 public void schedulePersist(@UserIdInt int userId, 907 @NonNull String packageName, @NonNull byte[] cookie) { 908 cancelPendingPersist(userId, packageName); 909 addPendingPersistCookie(userId, packageName, cookie); 910 sendMessageDelayed(obtainMessage(userId, packageName), 911 PERSIST_COOKIE_DELAY_MILLIS); 912 } 913 914 public @Nullable byte[] getPendingPersistCookie(@UserIdInt int userId, 915 @NonNull String packageName) { 916 ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId); 917 if (pendingWorkForUser != null) { 918 return pendingWorkForUser.remove(packageName); 919 } 920 return null; 921 } 922 923 private void cancelPendingPersist(@UserIdInt int userId, 924 @NonNull String packageName) { 925 removePendingPersistCookie(userId, packageName); 926 removeMessages(userId, packageName); 927 } 928 929 private void addPendingPersistCookie(@UserIdInt int userId, 930 @NonNull String packageName, @NonNull byte[] cookie) { 931 ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId); 932 if (pendingWorkForUser == null) { 933 pendingWorkForUser = new ArrayMap<>(); 934 mPendingPersistCookies.put(userId, pendingWorkForUser); 935 } 936 pendingWorkForUser.put(packageName, cookie); 937 } 938 939 private byte[] removePendingPersistCookie(@UserIdInt int userId, 940 @NonNull String packageName) { 941 ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId); 942 byte[] cookie = null; 943 if (pendingWorkForUser != null) { 944 cookie = pendingWorkForUser.remove(packageName); 945 if (pendingWorkForUser.isEmpty()) { 946 mPendingPersistCookies.remove(userId); 947 } 948 } 949 return cookie; 950 } 951 952 @Override 953 public void handleMessage(Message message) { 954 int userId = message.what; 955 String packageName = (String) message.obj; 956 byte[] cookie = removePendingPersistCookie(userId, packageName); 957 persistInstantApplicationCookie(cookie, packageName, userId); 958 } 959 } 960} 961