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