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