InstantAppRegistry.java revision be9ffa15af9e1906e9ffb505768328d62d4a3793
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 (pkg.applicationInfo.isInstantApp()) { 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 (pkg.applicationInfo.isInstantApp()) { 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 PackageParser.Package pkg = mService.mPackages.valueAt(i); 537 if (!pkg.applicationInfo.isInstantApp()) { 538 continue; 539 } 540 InstantAppInfo info = createInstantAppInfoForPackage( 541 pkg, userId, true); 542 if (info == null) { 543 continue; 544 } 545 if (result == null) { 546 result = new ArrayList<>(); 547 } 548 result.add(info); 549 } 550 551 return result; 552 } 553 554 private @NonNull 555 InstantAppInfo createInstantAppInfoForPackage( 556 @NonNull PackageParser.Package pkg, @UserIdInt int userId, 557 boolean addApplicationInfo) { 558 PackageSetting ps = (PackageSetting) pkg.mExtras; 559 if (ps == null) { 560 return null; 561 } 562 if (!ps.getInstalled(userId)) { 563 return null; 564 } 565 566 String[] requestedPermissions = new String[pkg.requestedPermissions.size()]; 567 pkg.requestedPermissions.toArray(requestedPermissions); 568 569 Set<String> permissions = ps.getPermissionsState().getPermissions(userId); 570 String[] grantedPermissions = new String[permissions.size()]; 571 permissions.toArray(grantedPermissions); 572 573 if (addApplicationInfo) { 574 return new InstantAppInfo(pkg.applicationInfo, 575 requestedPermissions, grantedPermissions); 576 } else { 577 return new InstantAppInfo(pkg.applicationInfo.packageName, 578 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()), 579 requestedPermissions, grantedPermissions); 580 } 581 } 582 583 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr( 584 @UserIdInt int userId) { 585 List<UninstalledInstantAppState> uninstalledAppStates = 586 getUninstalledInstantAppStatesLPr(userId); 587 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) { 588 return null; 589 } 590 591 List<InstantAppInfo> uninstalledApps = null; 592 final int stateCount = uninstalledAppStates.size(); 593 for (int i = 0; i < stateCount; i++) { 594 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 595 if (uninstalledApps == null) { 596 uninstalledApps = new ArrayList<>(); 597 } 598 uninstalledApps.add(uninstalledAppState.mInstantAppInfo); 599 } 600 return uninstalledApps; 601 } 602 603 private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName, 604 @UserIdInt int userId) { 605 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo( 606 packageName, userId); 607 if (appInfo == null) { 608 return; 609 } 610 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) { 611 return; 612 } 613 final long identity = Binder.clearCallingIdentity(); 614 try { 615 for (String grantedPermission : appInfo.getGrantedPermissions()) { 616 BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission); 617 if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) { 618 mService.grantRuntimePermission(packageName, grantedPermission, userId); 619 } 620 } 621 } finally { 622 Binder.restoreCallingIdentity(identity); 623 } 624 } 625 626 private @NonNull 627 InstantAppInfo peekOrParseUninstalledInstantAppInfo( 628 @NonNull String packageName, @UserIdInt int userId) { 629 if (mUninstalledInstantApps != null) { 630 List<UninstalledInstantAppState> uninstalledAppStates = 631 mUninstalledInstantApps.get(userId); 632 if (uninstalledAppStates != null) { 633 final int appCount = uninstalledAppStates.size(); 634 for (int i = 0; i < appCount; i++) { 635 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 636 if (uninstalledAppState.mInstantAppInfo 637 .getPackageName().equals(packageName)) { 638 return uninstalledAppState.mInstantAppInfo; 639 } 640 } 641 } 642 } 643 644 File metadataFile = new File(getInstantApplicationDir(packageName, userId), 645 INSTANT_APP_METADATA_FILE); 646 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile); 647 if (uninstalledAppState == null) { 648 return null; 649 } 650 651 return uninstalledAppState.mInstantAppInfo; 652 } 653 654 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr( 655 @UserIdInt int userId) { 656 List<UninstalledInstantAppState> uninstalledAppStates = null; 657 if (mUninstalledInstantApps != null) { 658 uninstalledAppStates = mUninstalledInstantApps.get(userId); 659 if (uninstalledAppStates != null) { 660 return uninstalledAppStates; 661 } 662 } 663 664 File instantAppsDir = getInstantApplicationsDir(userId); 665 if (instantAppsDir.exists()) { 666 File[] files = instantAppsDir.listFiles(); 667 if (files != null) { 668 for (File instantDir : files) { 669 if (!instantDir.isDirectory()) { 670 continue; 671 } 672 File metadataFile = new File(instantDir, 673 INSTANT_APP_METADATA_FILE); 674 UninstalledInstantAppState uninstalledAppState = 675 parseMetadataFile(metadataFile); 676 if (uninstalledAppState == null) { 677 continue; 678 } 679 if (uninstalledAppStates == null) { 680 uninstalledAppStates = new ArrayList<>(); 681 } 682 uninstalledAppStates.add(uninstalledAppState); 683 } 684 } 685 } 686 687 if (uninstalledAppStates != null) { 688 if (mUninstalledInstantApps == null) { 689 mUninstalledInstantApps = new SparseArray<>(); 690 } 691 mUninstalledInstantApps.put(userId, uninstalledAppStates); 692 } 693 694 return uninstalledAppStates; 695 } 696 697 private static @Nullable UninstalledInstantAppState parseMetadataFile( 698 @NonNull File metadataFile) { 699 if (!metadataFile.exists()) { 700 return null; 701 } 702 FileInputStream in; 703 try { 704 in = new AtomicFile(metadataFile).openRead(); 705 } catch (FileNotFoundException fnfe) { 706 Slog.i(LOG_TAG, "No instant metadata file"); 707 return null; 708 } 709 710 final File instantDir = metadataFile.getParentFile(); 711 final long timestamp = metadataFile.lastModified(); 712 final String packageName = instantDir.getName(); 713 714 try { 715 XmlPullParser parser = Xml.newPullParser(); 716 parser.setInput(in, StandardCharsets.UTF_8.name()); 717 return new UninstalledInstantAppState( 718 parseMetadata(parser, packageName), timestamp); 719 } catch (XmlPullParserException | IOException e) { 720 throw new IllegalStateException("Failed parsing instant" 721 + " metadata file: " + metadataFile, e); 722 } finally { 723 IoUtils.closeQuietly(in); 724 } 725 } 726 727 private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg, 728 @UserIdInt int userId) { 729 File appDir = getInstantApplicationDir(pkg.packageName, userId); 730 String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest( 731 pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX; 732 return new File(appDir, cookieFile); 733 } 734 735 private static @Nullable File peekInstantCookieFile(@NonNull String packageName, 736 @UserIdInt int userId) { 737 File appDir = getInstantApplicationDir(packageName, userId); 738 if (!appDir.exists()) { 739 return null; 740 } 741 File[] files = appDir.listFiles(); 742 if (files == null) { 743 return null; 744 } 745 for (File file : files) { 746 if (!file.isDirectory() 747 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX) 748 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) { 749 return file; 750 } 751 } 752 return null; 753 } 754 755 private static @Nullable 756 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser, 757 @NonNull String packageName) 758 throws IOException, XmlPullParserException { 759 final int outerDepth = parser.getDepth(); 760 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 761 if (TAG_PACKAGE.equals(parser.getName())) { 762 return parsePackage(parser, packageName); 763 } 764 } 765 return null; 766 } 767 768 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser, 769 @NonNull String packageName) 770 throws IOException, XmlPullParserException { 771 String label = parser.getAttributeValue(null, ATTR_LABEL); 772 773 List<String> outRequestedPermissions = new ArrayList<>(); 774 List<String> outGrantedPermissions = new ArrayList<>(); 775 776 final int outerDepth = parser.getDepth(); 777 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 778 if (TAG_PERMISSIONS.equals(parser.getName())) { 779 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions); 780 } 781 } 782 783 String[] requestedPermissions = new String[outRequestedPermissions.size()]; 784 outRequestedPermissions.toArray(requestedPermissions); 785 786 String[] grantedPermissions = new String[outGrantedPermissions.size()]; 787 outGrantedPermissions.toArray(grantedPermissions); 788 789 return new InstantAppInfo(packageName, label, 790 requestedPermissions, grantedPermissions); 791 } 792 793 private static void parsePermissions(@NonNull XmlPullParser parser, 794 @NonNull List<String> outRequestedPermissions, 795 @NonNull List<String> outGrantedPermissions) 796 throws IOException, XmlPullParserException { 797 final int outerDepth = parser.getDepth(); 798 while (XmlUtils.nextElementWithin(parser,outerDepth)) { 799 if (TAG_PERMISSION.equals(parser.getName())) { 800 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME); 801 outRequestedPermissions.add(permission); 802 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) { 803 outGrantedPermissions.add(permission); 804 } 805 } 806 } 807 } 808 809 private void writeUninstalledInstantAppMetadata( 810 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) { 811 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId); 812 if (!appDir.exists() && !appDir.mkdirs()) { 813 return; 814 } 815 816 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE); 817 818 AtomicFile destination = new AtomicFile(metadataFile); 819 FileOutputStream out = null; 820 try { 821 out = destination.startWrite(); 822 823 XmlSerializer serializer = Xml.newSerializer(); 824 serializer.setOutput(out, StandardCharsets.UTF_8.name()); 825 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 826 827 serializer.startDocument(null, true); 828 829 serializer.startTag(null, TAG_PACKAGE); 830 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel( 831 mService.mContext.getPackageManager()).toString()); 832 833 serializer.startTag(null, TAG_PERMISSIONS); 834 for (String permission : instantApp.getRequestedPermissions()) { 835 serializer.startTag(null, TAG_PERMISSION); 836 serializer.attribute(null, ATTR_NAME, permission); 837 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) { 838 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true)); 839 } 840 serializer.endTag(null, TAG_PERMISSION); 841 } 842 serializer.endTag(null, TAG_PERMISSIONS); 843 844 serializer.endTag(null, TAG_PACKAGE); 845 846 serializer.endDocument(); 847 destination.finishWrite(out); 848 } catch (Throwable t) { 849 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t); 850 destination.failWrite(out); 851 } finally { 852 IoUtils.closeQuietly(out); 853 } 854 } 855 856 private static @NonNull File getInstantApplicationsDir(int userId) { 857 return new File(Environment.getUserSystemDirectory(userId), 858 INSTANT_APPS_FOLDER); 859 } 860 861 private static @NonNull File getInstantApplicationDir(String packageName, int userId) { 862 return new File (getInstantApplicationsDir(userId), packageName); 863 } 864 865 private static void deleteDir(@NonNull File dir) { 866 File[] files = dir.listFiles(); 867 if (files != null) { 868 for (File file : files) { 869 deleteDir(file); 870 } 871 } 872 dir.delete(); 873 } 874 875 private static final class UninstalledInstantAppState { 876 final InstantAppInfo mInstantAppInfo; 877 final long mTimestamp; 878 879 public UninstalledInstantAppState(InstantAppInfo instantApp, 880 long timestamp) { 881 mInstantAppInfo = instantApp; 882 mTimestamp = timestamp; 883 } 884 } 885 886 private final class CookiePersistence extends Handler { 887 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */ 888 889 // In case you wonder why we stash the cookies aside, we use 890 // the user id for the message id and the package for the payload. 891 // Handler allows removing messages by id and tag where the 892 // tag is is compared using ==. So to allow cancelling the 893 // pending persistence for an app under a given user we use 894 // the fact that package names are interned in the system 895 // process so the == comparison would match and we end up 896 // with a way to cancel persisting the cookie for a user 897 // and package. 898 private final SparseArray<ArrayMap<String, byte[]>> mPendingPersistCookies = 899 new SparseArray<>(); 900 901 public CookiePersistence(Looper looper) { 902 super(looper); 903 } 904 905 public void schedulePersist(@UserIdInt int userId, 906 @NonNull String packageName, @NonNull byte[] cookie) { 907 cancelPendingPersist(userId, packageName); 908 addPendingPersistCookie(userId, packageName, cookie); 909 sendMessageDelayed(obtainMessage(userId, packageName), 910 PERSIST_COOKIE_DELAY_MILLIS); 911 } 912 913 public @Nullable byte[] getPendingPersistCookie(@UserIdInt int userId, 914 @NonNull String packageName) { 915 ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId); 916 if (pendingWorkForUser != null) { 917 return pendingWorkForUser.remove(packageName); 918 } 919 return null; 920 } 921 922 private void cancelPendingPersist(@UserIdInt int userId, 923 @NonNull String packageName) { 924 removePendingPersistCookie(userId, packageName); 925 removeMessages(userId, packageName); 926 } 927 928 private void addPendingPersistCookie(@UserIdInt int userId, 929 @NonNull String packageName, @NonNull byte[] cookie) { 930 ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId); 931 if (pendingWorkForUser == null) { 932 pendingWorkForUser = new ArrayMap<>(); 933 mPendingPersistCookies.put(userId, pendingWorkForUser); 934 } 935 pendingWorkForUser.put(packageName, cookie); 936 } 937 938 private byte[] removePendingPersistCookie(@UserIdInt int userId, 939 @NonNull String packageName) { 940 ArrayMap<String, byte[]> pendingWorkForUser = mPendingPersistCookies.get(userId); 941 byte[] cookie = null; 942 if (pendingWorkForUser != null) { 943 cookie = pendingWorkForUser.remove(packageName); 944 if (pendingWorkForUser.isEmpty()) { 945 mPendingPersistCookies.remove(userId); 946 } 947 } 948 return cookie; 949 } 950 951 @Override 952 public void handleMessage(Message message) { 953 int userId = message.what; 954 String packageName = (String) message.obj; 955 byte[] cookie = removePendingPersistCookie(userId, packageName); 956 persistInstantApplicationCookie(cookie, packageName, userId); 957 } 958 } 959} 960