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