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 17 package com.android.server.pm; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UserIdInt; 22 import android.content.Intent; 23 import android.content.pm.InstantAppInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageParser; 26 import android.graphics.Bitmap; 27 import android.graphics.BitmapFactory; 28 import android.graphics.Canvas; 29 import android.graphics.drawable.BitmapDrawable; 30 import android.graphics.drawable.Drawable; 31 import android.os.Binder; 32 import android.os.Environment; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.os.UserHandle; 37 import android.os.storage.StorageManager; 38 import android.provider.Settings; 39 import android.util.ArrayMap; 40 import android.util.AtomicFile; 41 import android.util.ByteStringUtils; 42 import android.util.PackageUtils; 43 import android.util.Slog; 44 import android.util.SparseArray; 45 import android.util.SparseBooleanArray; 46 import android.util.Xml; 47 48 import com.android.internal.annotations.GuardedBy; 49 import com.android.internal.os.BackgroundThread; 50 import com.android.internal.os.SomeArgs; 51 import com.android.internal.util.ArrayUtils; 52 import com.android.internal.util.XmlUtils; 53 54 import libcore.io.IoUtils; 55 56 import org.xmlpull.v1.XmlPullParser; 57 import org.xmlpull.v1.XmlPullParserException; 58 import org.xmlpull.v1.XmlSerializer; 59 60 import java.io.File; 61 import java.io.FileInputStream; 62 import java.io.FileNotFoundException; 63 import java.io.FileOutputStream; 64 import java.io.IOException; 65 import java.nio.charset.StandardCharsets; 66 import java.security.SecureRandom; 67 import java.util.ArrayList; 68 import java.util.List; 69 import java.util.Locale; 70 import java.util.Set; 71 import java.util.function.Predicate; 72 73 /** 74 * This class is a part of the package manager service that is responsible 75 * for managing data associated with instant apps such as cached uninstalled 76 * instant apps and instant apps' cookies. In addition it is responsible for 77 * pruning installed instant apps and meta-data for uninstalled instant apps 78 * when free space is needed. 79 */ 80 class InstantAppRegistry { 81 private static final boolean DEBUG = false; 82 83 private static final String LOG_TAG = "InstantAppRegistry"; 84 85 static final long DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD = 86 DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */ 87 88 private static final long DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD = 89 DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */ 90 91 static final long DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD = 92 DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */ 93 94 private static final long DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD = 95 DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */ 96 97 private static final String INSTANT_APPS_FOLDER = "instant"; 98 private static final String INSTANT_APP_ICON_FILE = "icon.png"; 99 private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_"; 100 private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat"; 101 private static final String INSTANT_APP_METADATA_FILE = "metadata.xml"; 102 private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id"; 103 104 private static final String TAG_PACKAGE = "package"; 105 private static final String TAG_PERMISSIONS = "permissions"; 106 private static final String TAG_PERMISSION = "permission"; 107 108 private static final String ATTR_LABEL = "label"; 109 private static final String ATTR_NAME = "name"; 110 private static final String ATTR_GRANTED = "granted"; 111 112 private final PackageManagerService mService; 113 private final CookiePersistence mCookiePersistence; 114 115 /** State for uninstalled instant apps */ 116 @GuardedBy("mService.mPackages") 117 private SparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps; 118 119 /** 120 * Automatic grants for access to instant app metadata. 121 * The key is the target application UID. 122 * The value is a set of instant app UIDs. 123 * UserID -> TargetAppId -> InstantAppId 124 */ 125 @GuardedBy("mService.mPackages") 126 private SparseArray<SparseArray<SparseBooleanArray>> mInstantGrants; 127 128 /** The set of all installed instant apps. UserID -> AppID */ 129 @GuardedBy("mService.mPackages") 130 private SparseArray<SparseBooleanArray> mInstalledInstantAppUids; 131 InstantAppRegistry(PackageManagerService service)132 public InstantAppRegistry(PackageManagerService service) { 133 mService = service; 134 mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper()); 135 } 136 137 @GuardedBy("mService.mPackages") getInstantAppCookieLPw(@onNull String packageName, @UserIdInt int userId)138 public byte[] getInstantAppCookieLPw(@NonNull String packageName, 139 @UserIdInt int userId) { 140 // Only installed packages can get their own cookie 141 PackageParser.Package pkg = mService.mPackages.get(packageName); 142 if (pkg == null) { 143 return null; 144 } 145 146 byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId); 147 if (pendingCookie != null) { 148 return pendingCookie; 149 } 150 File cookieFile = peekInstantCookieFile(packageName, userId); 151 if (cookieFile != null && cookieFile.exists()) { 152 try { 153 return IoUtils.readFileAsByteArray(cookieFile.toString()); 154 } catch (IOException e) { 155 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile); 156 } 157 } 158 return null; 159 } 160 161 @GuardedBy("mService.mPackages") setInstantAppCookieLPw(@onNull String packageName, @Nullable byte[] cookie, @UserIdInt int userId)162 public boolean setInstantAppCookieLPw(@NonNull String packageName, 163 @Nullable byte[] cookie, @UserIdInt int userId) { 164 if (cookie != null && cookie.length > 0) { 165 final int maxCookieSize = mService.mContext.getPackageManager() 166 .getInstantAppCookieMaxBytes(); 167 if (cookie.length > maxCookieSize) { 168 Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size " 169 + cookie.length + " bytes while max size is " + maxCookieSize); 170 return false; 171 } 172 } 173 174 // Only an installed package can set its own cookie 175 PackageParser.Package pkg = mService.mPackages.get(packageName); 176 if (pkg == null) { 177 return false; 178 } 179 180 mCookiePersistence.schedulePersistLPw(userId, pkg, cookie); 181 return true; 182 } 183 persistInstantApplicationCookie(@ullable byte[] cookie, @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId)184 private void persistInstantApplicationCookie(@Nullable byte[] cookie, 185 @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) { 186 synchronized (mService.mPackages) { 187 File appDir = getInstantApplicationDir(packageName, userId); 188 if (!appDir.exists() && !appDir.mkdirs()) { 189 Slog.e(LOG_TAG, "Cannot create instant app cookie directory"); 190 return; 191 } 192 193 if (cookieFile.exists() && !cookieFile.delete()) { 194 Slog.e(LOG_TAG, "Cannot delete instant app cookie file"); 195 } 196 197 // No cookie or an empty one means delete - done 198 if (cookie == null || cookie.length <= 0) { 199 return; 200 } 201 } 202 try (FileOutputStream fos = new FileOutputStream(cookieFile)) { 203 fos.write(cookie, 0, cookie.length); 204 } catch (IOException e) { 205 Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e); 206 } 207 } 208 getInstantAppIconLPw(@onNull String packageName, @UserIdInt int userId)209 public Bitmap getInstantAppIconLPw(@NonNull String packageName, 210 @UserIdInt int userId) { 211 File iconFile = new File(getInstantApplicationDir(packageName, userId), 212 INSTANT_APP_ICON_FILE); 213 if (iconFile.exists()) { 214 return BitmapFactory.decodeFile(iconFile.toString()); 215 } 216 return null; 217 } 218 getInstantAppAndroidIdLPw(@onNull String packageName, @UserIdInt int userId)219 public String getInstantAppAndroidIdLPw(@NonNull String packageName, 220 @UserIdInt int userId) { 221 File idFile = new File(getInstantApplicationDir(packageName, userId), 222 INSTANT_APP_ANDROID_ID_FILE); 223 if (idFile.exists()) { 224 try { 225 return IoUtils.readFileAsString(idFile.getAbsolutePath()); 226 } catch (IOException e) { 227 Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e); 228 } 229 } 230 return generateInstantAppAndroidIdLPw(packageName, userId); 231 } 232 generateInstantAppAndroidIdLPw(@onNull String packageName, @UserIdInt int userId)233 private String generateInstantAppAndroidIdLPw(@NonNull String packageName, 234 @UserIdInt int userId) { 235 byte[] randomBytes = new byte[8]; 236 new SecureRandom().nextBytes(randomBytes); 237 String id = ByteStringUtils.toHexString(randomBytes).toLowerCase(Locale.US); 238 File appDir = getInstantApplicationDir(packageName, userId); 239 if (!appDir.exists() && !appDir.mkdirs()) { 240 Slog.e(LOG_TAG, "Cannot create instant app cookie directory"); 241 return id; 242 } 243 File idFile = new File(getInstantApplicationDir(packageName, userId), 244 INSTANT_APP_ANDROID_ID_FILE); 245 try (FileOutputStream fos = new FileOutputStream(idFile)) { 246 fos.write(id.getBytes()); 247 } catch (IOException e) { 248 Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e); 249 } 250 return id; 251 252 } 253 254 @GuardedBy("mService.mPackages") getInstantAppsLPr(@serIdInt int userId)255 public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) { 256 List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId); 257 List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId); 258 if (installedApps != null) { 259 if (uninstalledApps != null) { 260 installedApps.addAll(uninstalledApps); 261 } 262 return installedApps; 263 } 264 return uninstalledApps; 265 } 266 267 @GuardedBy("mService.mPackages") onPackageInstalledLPw(@onNull PackageParser.Package pkg, @NonNull int[] userIds)268 public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) { 269 PackageSetting ps = (PackageSetting) pkg.mExtras; 270 if (ps == null) { 271 return; 272 } 273 274 for (int userId : userIds) { 275 // Ignore not installed apps 276 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) { 277 continue; 278 } 279 280 // Propagate permissions before removing any state 281 propagateInstantAppPermissionsIfNeeded(pkg, userId); 282 283 // Track instant apps 284 if (ps.getInstantApp(userId)) { 285 addInstantAppLPw(userId, ps.appId); 286 } 287 288 // Remove the in-memory state 289 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> 290 state.mInstantAppInfo.getPackageName().equals(pkg.packageName), 291 userId); 292 293 // Remove the on-disk state except the cookie 294 File instantAppDir = getInstantApplicationDir(pkg.packageName, userId); 295 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete(); 296 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete(); 297 298 // If app signature changed - wipe the cookie 299 File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId); 300 if (currentCookieFile == null) { 301 continue; 302 } 303 304 String cookieName = currentCookieFile.getName(); 305 String currentCookieSha256 = 306 cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(), 307 cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length()); 308 309 // Before we used only the first signature to compute the SHA 256 but some 310 // apps could be singed by multiple certs and the cert order is undefined. 311 // We prefer the modern computation procedure where all certs are taken 312 // into account but also allow the value from the old computation to avoid 313 // data loss. 314 if (pkg.mSigningDetails.checkCapability(currentCookieSha256, 315 PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { 316 return; 317 } 318 319 // For backwards compatibility we accept match based on any signature, since we may have 320 // recorded only the first for multiply-signed packages 321 final String[] signaturesSha256Digests = 322 PackageUtils.computeSignaturesSha256Digests(pkg.mSigningDetails.signatures); 323 for (String s : signaturesSha256Digests) { 324 if (s.equals(currentCookieSha256)) { 325 return; 326 } 327 } 328 329 // Sorry, you are out of luck - different signatures - nuke data 330 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName 331 + " changed - dropping cookie"); 332 // Make sure a pending write for the old signed app is cancelled 333 mCookiePersistence.cancelPendingPersistLPw(pkg, userId); 334 currentCookieFile.delete(); 335 } 336 } 337 338 @GuardedBy("mService.mPackages") onPackageUninstalledLPw(@onNull PackageParser.Package pkg, @NonNull int[] userIds)339 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg, 340 @NonNull int[] userIds) { 341 PackageSetting ps = (PackageSetting) pkg.mExtras; 342 if (ps == null) { 343 return; 344 } 345 346 for (int userId : userIds) { 347 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) { 348 continue; 349 } 350 351 if (ps.getInstantApp(userId)) { 352 // Add a record for an uninstalled instant app 353 addUninstalledInstantAppLPw(pkg, userId); 354 removeInstantAppLPw(userId, ps.appId); 355 } else { 356 // Deleting an app prunes all instant state such as cookie 357 deleteDir(getInstantApplicationDir(pkg.packageName, userId)); 358 mCookiePersistence.cancelPendingPersistLPw(pkg, userId); 359 removeAppLPw(userId, ps.appId); 360 } 361 } 362 } 363 364 @GuardedBy("mService.mPackages") onUserRemovedLPw(int userId)365 public void onUserRemovedLPw(int userId) { 366 if (mUninstalledInstantApps != null) { 367 mUninstalledInstantApps.remove(userId); 368 if (mUninstalledInstantApps.size() <= 0) { 369 mUninstalledInstantApps = null; 370 } 371 } 372 if (mInstalledInstantAppUids != null) { 373 mInstalledInstantAppUids.remove(userId); 374 if (mInstalledInstantAppUids.size() <= 0) { 375 mInstalledInstantAppUids = null; 376 } 377 } 378 if (mInstantGrants != null) { 379 mInstantGrants.remove(userId); 380 if (mInstantGrants.size() <= 0) { 381 mInstantGrants = null; 382 } 383 } 384 deleteDir(getInstantApplicationsDir(userId)); 385 } 386 isInstantAccessGranted(@serIdInt int userId, int targetAppId, int instantAppId)387 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId, 388 int instantAppId) { 389 if (mInstantGrants == null) { 390 return false; 391 } 392 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 393 if (targetAppList == null) { 394 return false; 395 } 396 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId); 397 if (instantGrantList == null) { 398 return false; 399 } 400 return instantGrantList.get(instantAppId); 401 } 402 403 @GuardedBy("mService.mPackages") grantInstantAccessLPw(@serIdInt int userId, @Nullable Intent intent, int targetAppId, int instantAppId)404 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent, 405 int targetAppId, int instantAppId) { 406 if (mInstalledInstantAppUids == null) { 407 return; // no instant apps installed; no need to grant 408 } 409 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 410 if (instantAppList == null || !instantAppList.get(instantAppId)) { 411 return; // instant app id isn't installed; no need to grant 412 } 413 if (instantAppList.get(targetAppId)) { 414 return; // target app id is an instant app; no need to grant 415 } 416 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { 417 final Set<String> categories = intent.getCategories(); 418 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) { 419 return; // launched via VIEW/BROWSABLE intent; no need to grant 420 } 421 } 422 if (mInstantGrants == null) { 423 mInstantGrants = new SparseArray<>(); 424 } 425 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 426 if (targetAppList == null) { 427 targetAppList = new SparseArray<>(); 428 mInstantGrants.put(userId, targetAppList); 429 } 430 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId); 431 if (instantGrantList == null) { 432 instantGrantList = new SparseBooleanArray(); 433 targetAppList.put(targetAppId, instantGrantList); 434 } 435 instantGrantList.put(instantAppId, true /*granted*/); 436 } 437 438 @GuardedBy("mService.mPackages") addInstantAppLPw(@serIdInt int userId, int instantAppId)439 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) { 440 if (mInstalledInstantAppUids == null) { 441 mInstalledInstantAppUids = new SparseArray<>(); 442 } 443 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 444 if (instantAppList == null) { 445 instantAppList = new SparseBooleanArray(); 446 mInstalledInstantAppUids.put(userId, instantAppList); 447 } 448 instantAppList.put(instantAppId, true /*installed*/); 449 } 450 451 @GuardedBy("mService.mPackages") removeInstantAppLPw(@serIdInt int userId, int instantAppId)452 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) { 453 // remove from the installed list 454 if (mInstalledInstantAppUids == null) { 455 return; // no instant apps on the system 456 } 457 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId); 458 if (instantAppList == null) { 459 return; 460 } 461 462 instantAppList.delete(instantAppId); 463 464 // remove any grants 465 if (mInstantGrants == null) { 466 return; // no grants on the system 467 } 468 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 469 if (targetAppList == null) { 470 return; // no grants for this user 471 } 472 for (int i = targetAppList.size() - 1; i >= 0; --i) { 473 targetAppList.valueAt(i).delete(instantAppId); 474 } 475 } 476 477 @GuardedBy("mService.mPackages") removeAppLPw(@serIdInt int userId, int targetAppId)478 private void removeAppLPw(@UserIdInt int userId, int targetAppId) { 479 // remove from the installed list 480 if (mInstantGrants == null) { 481 return; // no grants on the system 482 } 483 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId); 484 if (targetAppList == null) { 485 return; // no grants for this user 486 } 487 targetAppList.delete(targetAppId); 488 } 489 490 @GuardedBy("mService.mPackages") addUninstalledInstantAppLPw(@onNull PackageParser.Package pkg, @UserIdInt int userId)491 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg, 492 @UserIdInt int userId) { 493 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage( 494 pkg, userId, false); 495 if (uninstalledApp == null) { 496 return; 497 } 498 if (mUninstalledInstantApps == null) { 499 mUninstalledInstantApps = new SparseArray<>(); 500 } 501 List<UninstalledInstantAppState> uninstalledAppStates = 502 mUninstalledInstantApps.get(userId); 503 if (uninstalledAppStates == null) { 504 uninstalledAppStates = new ArrayList<>(); 505 mUninstalledInstantApps.put(userId, uninstalledAppStates); 506 } 507 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState( 508 uninstalledApp, System.currentTimeMillis()); 509 uninstalledAppStates.add(uninstalledAppState); 510 511 writeUninstalledInstantAppMetadata(uninstalledApp, userId); 512 writeInstantApplicationIconLPw(pkg, userId); 513 } 514 writeInstantApplicationIconLPw(@onNull PackageParser.Package pkg, @UserIdInt int userId)515 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg, 516 @UserIdInt int userId) { 517 File appDir = getInstantApplicationDir(pkg.packageName, userId); 518 if (!appDir.exists()) { 519 return; 520 } 521 522 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager()); 523 524 final Bitmap bitmap; 525 if (icon instanceof BitmapDrawable) { 526 bitmap = ((BitmapDrawable) icon).getBitmap(); 527 } else { 528 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), 529 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 530 Canvas canvas = new Canvas(bitmap); 531 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 532 icon.draw(canvas); 533 } 534 535 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId), 536 INSTANT_APP_ICON_FILE); 537 538 try (FileOutputStream out = new FileOutputStream(iconFile)) { 539 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 540 } catch (Exception e) { 541 Slog.e(LOG_TAG, "Error writing instant app icon", e); 542 } 543 } 544 545 @GuardedBy("mService.mPackages") hasInstantApplicationMetadataLPr(String packageName, int userId)546 boolean hasInstantApplicationMetadataLPr(String packageName, int userId) { 547 return hasUninstalledInstantAppStateLPr(packageName, userId) 548 || hasInstantAppMetadataLPr(packageName, userId); 549 } 550 551 @GuardedBy("mService.mPackages") deleteInstantApplicationMetadataLPw(@onNull String packageName, @UserIdInt int userId)552 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName, 553 @UserIdInt int userId) { 554 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> 555 state.mInstantAppInfo.getPackageName().equals(packageName), 556 userId); 557 558 File instantAppDir = getInstantApplicationDir(packageName, userId); 559 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete(); 560 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete(); 561 new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete(); 562 File cookie = peekInstantCookieFile(packageName, userId); 563 if (cookie != null) { 564 cookie.delete(); 565 } 566 } 567 568 @GuardedBy("mService.mPackages") removeUninstalledInstantAppStateLPw( @onNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId)569 private void removeUninstalledInstantAppStateLPw( 570 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) { 571 if (mUninstalledInstantApps == null) { 572 return; 573 } 574 List<UninstalledInstantAppState> uninstalledAppStates = 575 mUninstalledInstantApps.get(userId); 576 if (uninstalledAppStates == null) { 577 return; 578 } 579 final int appCount = uninstalledAppStates.size(); 580 for (int i = appCount - 1; i >= 0; --i) { 581 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 582 if (!criteria.test(uninstalledAppState)) { 583 continue; 584 } 585 uninstalledAppStates.remove(i); 586 if (uninstalledAppStates.isEmpty()) { 587 mUninstalledInstantApps.remove(userId); 588 if (mUninstalledInstantApps.size() <= 0) { 589 mUninstalledInstantApps = null; 590 } 591 return; 592 } 593 } 594 } 595 596 @GuardedBy("mService.mPackages") hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId)597 private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) { 598 if (mUninstalledInstantApps == null) { 599 return false; 600 } 601 final List<UninstalledInstantAppState> uninstalledAppStates = 602 mUninstalledInstantApps.get(userId); 603 if (uninstalledAppStates == null) { 604 return false; 605 } 606 final int appCount = uninstalledAppStates.size(); 607 for (int i = 0; i < appCount; i++) { 608 final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 609 if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) { 610 return true; 611 } 612 } 613 return false; 614 } 615 hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId)616 private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) { 617 final File instantAppDir = getInstantApplicationDir(packageName, userId); 618 return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists() 619 || new File(instantAppDir, INSTANT_APP_ICON_FILE).exists() 620 || new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).exists() 621 || peekInstantCookieFile(packageName, userId) != null; 622 } 623 pruneInstantApps()624 void pruneInstantApps() { 625 final long maxInstalledCacheDuration = Settings.Global.getLong( 626 mService.mContext.getContentResolver(), 627 Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, 628 DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); 629 630 final long maxUninstalledCacheDuration = Settings.Global.getLong( 631 mService.mContext.getContentResolver(), 632 Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD, 633 DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD); 634 635 try { 636 pruneInstantApps(Long.MAX_VALUE, 637 maxInstalledCacheDuration, maxUninstalledCacheDuration); 638 } catch (IOException e) { 639 Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e); 640 } 641 } 642 pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration)643 boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) { 644 try { 645 return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE); 646 } catch (IOException e) { 647 Slog.e(LOG_TAG, "Error pruning installed instant apps", e); 648 return false; 649 } 650 } 651 pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration)652 boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) { 653 try { 654 return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration); 655 } catch (IOException e) { 656 Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e); 657 return false; 658 } 659 } 660 661 /** 662 * Prunes instant apps until there is enough <code>neededSpace</code>. Both 663 * installed and uninstalled instant apps are pruned that are older than 664 * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code> 665 * respectively. All times are in milliseconds. 666 * 667 * @param neededSpace The space to ensure is free. 668 * @param maxInstalledCacheDuration The max duration for caching installed apps in millis. 669 * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis. 670 * @return Whether enough space was freed. 671 * 672 * @throws IOException 673 */ pruneInstantApps(long neededSpace, long maxInstalledCacheDuration, long maxUninstalledCacheDuration)674 private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration, 675 long maxUninstalledCacheDuration) throws IOException { 676 final StorageManager storage = mService.mContext.getSystemService(StorageManager.class); 677 final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL); 678 679 if (file.getUsableSpace() >= neededSpace) { 680 return true; 681 } 682 683 List<String> packagesToDelete = null; 684 685 final int[] allUsers; 686 final long now = System.currentTimeMillis(); 687 688 // Prune first installed instant apps 689 synchronized (mService.mPackages) { 690 allUsers = PackageManagerService.sUserManager.getUserIds(); 691 692 final int packageCount = mService.mPackages.size(); 693 for (int i = 0; i < packageCount; i++) { 694 final PackageParser.Package pkg = mService.mPackages.valueAt(i); 695 if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) { 696 continue; 697 } 698 if (!(pkg.mExtras instanceof PackageSetting)) { 699 continue; 700 } 701 final PackageSetting ps = (PackageSetting) pkg.mExtras; 702 boolean installedOnlyAsInstantApp = false; 703 for (int userId : allUsers) { 704 if (ps.getInstalled(userId)) { 705 if (ps.getInstantApp(userId)) { 706 installedOnlyAsInstantApp = true; 707 } else { 708 installedOnlyAsInstantApp = false; 709 break; 710 } 711 } 712 } 713 if (installedOnlyAsInstantApp) { 714 if (packagesToDelete == null) { 715 packagesToDelete = new ArrayList<>(); 716 } 717 packagesToDelete.add(pkg.packageName); 718 } 719 } 720 721 if (packagesToDelete != null) { 722 packagesToDelete.sort((String lhs, String rhs) -> { 723 final PackageParser.Package lhsPkg = mService.mPackages.get(lhs); 724 final PackageParser.Package rhsPkg = mService.mPackages.get(rhs); 725 if (lhsPkg == null && rhsPkg == null) { 726 return 0; 727 } else if (lhsPkg == null) { 728 return -1; 729 } else if (rhsPkg == null) { 730 return 1; 731 } else { 732 if (lhsPkg.getLatestPackageUseTimeInMills() > 733 rhsPkg.getLatestPackageUseTimeInMills()) { 734 return 1; 735 } else if (lhsPkg.getLatestPackageUseTimeInMills() < 736 rhsPkg.getLatestPackageUseTimeInMills()) { 737 return -1; 738 } else { 739 if (lhsPkg.mExtras instanceof PackageSetting 740 && rhsPkg.mExtras instanceof PackageSetting) { 741 final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras; 742 final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras; 743 if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) { 744 return 1; 745 } else { 746 return -1; 747 } 748 } else { 749 return 0; 750 } 751 } 752 } 753 }); 754 } 755 } 756 757 if (packagesToDelete != null) { 758 final int packageCount = packagesToDelete.size(); 759 for (int i = 0; i < packageCount; i++) { 760 final String packageToDelete = packagesToDelete.get(i); 761 if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST, 762 UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS) 763 == PackageManager.DELETE_SUCCEEDED) { 764 if (file.getUsableSpace() >= neededSpace) { 765 return true; 766 } 767 } 768 } 769 } 770 771 // Prune uninstalled instant apps 772 synchronized (mService.mPackages) { 773 // TODO: Track last used time for uninstalled instant apps for better pruning 774 for (int userId : UserManagerService.getInstance().getUserIds()) { 775 // Prune in-memory state 776 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> { 777 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp; 778 return (elapsedCachingMillis > maxUninstalledCacheDuration); 779 }, userId); 780 781 // Prune on-disk state 782 File instantAppsDir = getInstantApplicationsDir(userId); 783 if (!instantAppsDir.exists()) { 784 continue; 785 } 786 File[] files = instantAppsDir.listFiles(); 787 if (files == null) { 788 continue; 789 } 790 for (File instantDir : files) { 791 if (!instantDir.isDirectory()) { 792 continue; 793 } 794 795 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE); 796 if (!metadataFile.exists()) { 797 continue; 798 } 799 800 final long elapsedCachingMillis = System.currentTimeMillis() 801 - metadataFile.lastModified(); 802 if (elapsedCachingMillis > maxUninstalledCacheDuration) { 803 deleteDir(instantDir); 804 if (file.getUsableSpace() >= neededSpace) { 805 return true; 806 } 807 } 808 } 809 } 810 } 811 812 return false; 813 } 814 815 @GuardedBy("mService.mPackages") getInstalledInstantApplicationsLPr( @serIdInt int userId)816 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr( 817 @UserIdInt int userId) { 818 List<InstantAppInfo> result = null; 819 820 final int packageCount = mService.mPackages.size(); 821 for (int i = 0; i < packageCount; i++) { 822 final PackageParser.Package pkg = mService.mPackages.valueAt(i); 823 final PackageSetting ps = (PackageSetting) pkg.mExtras; 824 if (ps == null || !ps.getInstantApp(userId)) { 825 continue; 826 } 827 final InstantAppInfo info = createInstantAppInfoForPackage( 828 pkg, userId, true); 829 if (info == null) { 830 continue; 831 } 832 if (result == null) { 833 result = new ArrayList<>(); 834 } 835 result.add(info); 836 } 837 838 return result; 839 } 840 841 private @NonNull createInstantAppInfoForPackage( @onNull PackageParser.Package pkg, @UserIdInt int userId, boolean addApplicationInfo)842 InstantAppInfo createInstantAppInfoForPackage( 843 @NonNull PackageParser.Package pkg, @UserIdInt int userId, 844 boolean addApplicationInfo) { 845 PackageSetting ps = (PackageSetting) pkg.mExtras; 846 if (ps == null) { 847 return null; 848 } 849 if (!ps.getInstalled(userId)) { 850 return null; 851 } 852 853 String[] requestedPermissions = new String[pkg.requestedPermissions.size()]; 854 pkg.requestedPermissions.toArray(requestedPermissions); 855 856 Set<String> permissions = ps.getPermissionsState().getPermissions(userId); 857 String[] grantedPermissions = new String[permissions.size()]; 858 permissions.toArray(grantedPermissions); 859 860 if (addApplicationInfo) { 861 return new InstantAppInfo(pkg.applicationInfo, 862 requestedPermissions, grantedPermissions); 863 } else { 864 return new InstantAppInfo(pkg.applicationInfo.packageName, 865 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()), 866 requestedPermissions, grantedPermissions); 867 } 868 } 869 870 @GuardedBy("mService.mPackages") getUninstalledInstantApplicationsLPr( @serIdInt int userId)871 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr( 872 @UserIdInt int userId) { 873 List<UninstalledInstantAppState> uninstalledAppStates = 874 getUninstalledInstantAppStatesLPr(userId); 875 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) { 876 return null; 877 } 878 879 List<InstantAppInfo> uninstalledApps = null; 880 final int stateCount = uninstalledAppStates.size(); 881 for (int i = 0; i < stateCount; i++) { 882 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 883 if (uninstalledApps == null) { 884 uninstalledApps = new ArrayList<>(); 885 } 886 uninstalledApps.add(uninstalledAppState.mInstantAppInfo); 887 } 888 return uninstalledApps; 889 } 890 propagateInstantAppPermissionsIfNeeded(@onNull PackageParser.Package pkg, @UserIdInt int userId)891 private void propagateInstantAppPermissionsIfNeeded(@NonNull PackageParser.Package pkg, 892 @UserIdInt int userId) { 893 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo( 894 pkg.packageName, userId); 895 if (appInfo == null) { 896 return; 897 } 898 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) { 899 return; 900 } 901 final long identity = Binder.clearCallingIdentity(); 902 try { 903 for (String grantedPermission : appInfo.getGrantedPermissions()) { 904 final boolean propagatePermission = 905 mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission); 906 if (propagatePermission && pkg.requestedPermissions.contains(grantedPermission)) { 907 mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId); 908 } 909 } 910 } finally { 911 Binder.restoreCallingIdentity(identity); 912 } 913 } 914 915 private @NonNull peekOrParseUninstalledInstantAppInfo( @onNull String packageName, @UserIdInt int userId)916 InstantAppInfo peekOrParseUninstalledInstantAppInfo( 917 @NonNull String packageName, @UserIdInt int userId) { 918 if (mUninstalledInstantApps != null) { 919 List<UninstalledInstantAppState> uninstalledAppStates = 920 mUninstalledInstantApps.get(userId); 921 if (uninstalledAppStates != null) { 922 final int appCount = uninstalledAppStates.size(); 923 for (int i = 0; i < appCount; i++) { 924 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i); 925 if (uninstalledAppState.mInstantAppInfo 926 .getPackageName().equals(packageName)) { 927 return uninstalledAppState.mInstantAppInfo; 928 } 929 } 930 } 931 } 932 933 File metadataFile = new File(getInstantApplicationDir(packageName, userId), 934 INSTANT_APP_METADATA_FILE); 935 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile); 936 if (uninstalledAppState == null) { 937 return null; 938 } 939 940 return uninstalledAppState.mInstantAppInfo; 941 } 942 943 @GuardedBy("mService.mPackages") getUninstalledInstantAppStatesLPr( @serIdInt int userId)944 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr( 945 @UserIdInt int userId) { 946 List<UninstalledInstantAppState> uninstalledAppStates = null; 947 if (mUninstalledInstantApps != null) { 948 uninstalledAppStates = mUninstalledInstantApps.get(userId); 949 if (uninstalledAppStates != null) { 950 return uninstalledAppStates; 951 } 952 } 953 954 File instantAppsDir = getInstantApplicationsDir(userId); 955 if (instantAppsDir.exists()) { 956 File[] files = instantAppsDir.listFiles(); 957 if (files != null) { 958 for (File instantDir : files) { 959 if (!instantDir.isDirectory()) { 960 continue; 961 } 962 File metadataFile = new File(instantDir, 963 INSTANT_APP_METADATA_FILE); 964 UninstalledInstantAppState uninstalledAppState = 965 parseMetadataFile(metadataFile); 966 if (uninstalledAppState == null) { 967 continue; 968 } 969 if (uninstalledAppStates == null) { 970 uninstalledAppStates = new ArrayList<>(); 971 } 972 uninstalledAppStates.add(uninstalledAppState); 973 } 974 } 975 } 976 977 if (uninstalledAppStates != null) { 978 if (mUninstalledInstantApps == null) { 979 mUninstalledInstantApps = new SparseArray<>(); 980 } 981 mUninstalledInstantApps.put(userId, uninstalledAppStates); 982 } 983 984 return uninstalledAppStates; 985 } 986 parseMetadataFile( @onNull File metadataFile)987 private static @Nullable UninstalledInstantAppState parseMetadataFile( 988 @NonNull File metadataFile) { 989 if (!metadataFile.exists()) { 990 return null; 991 } 992 FileInputStream in; 993 try { 994 in = new AtomicFile(metadataFile).openRead(); 995 } catch (FileNotFoundException fnfe) { 996 Slog.i(LOG_TAG, "No instant metadata file"); 997 return null; 998 } 999 1000 final File instantDir = metadataFile.getParentFile(); 1001 final long timestamp = metadataFile.lastModified(); 1002 final String packageName = instantDir.getName(); 1003 1004 try { 1005 XmlPullParser parser = Xml.newPullParser(); 1006 parser.setInput(in, StandardCharsets.UTF_8.name()); 1007 return new UninstalledInstantAppState( 1008 parseMetadata(parser, packageName), timestamp); 1009 } catch (XmlPullParserException | IOException e) { 1010 throw new IllegalStateException("Failed parsing instant" 1011 + " metadata file: " + metadataFile, e); 1012 } finally { 1013 IoUtils.closeQuietly(in); 1014 } 1015 } 1016 computeInstantCookieFile(@onNull String packageName, @NonNull String sha256Digest, @UserIdInt int userId)1017 private static @NonNull File computeInstantCookieFile(@NonNull String packageName, 1018 @NonNull String sha256Digest, @UserIdInt int userId) { 1019 final File appDir = getInstantApplicationDir(packageName, userId); 1020 final String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX 1021 + sha256Digest + INSTANT_APP_COOKIE_FILE_SIFFIX; 1022 return new File(appDir, cookieFile); 1023 } 1024 peekInstantCookieFile(@onNull String packageName, @UserIdInt int userId)1025 private static @Nullable File peekInstantCookieFile(@NonNull String packageName, 1026 @UserIdInt int userId) { 1027 File appDir = getInstantApplicationDir(packageName, userId); 1028 if (!appDir.exists()) { 1029 return null; 1030 } 1031 File[] files = appDir.listFiles(); 1032 if (files == null) { 1033 return null; 1034 } 1035 for (File file : files) { 1036 if (!file.isDirectory() 1037 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX) 1038 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) { 1039 return file; 1040 } 1041 } 1042 return null; 1043 } 1044 1045 private static @Nullable parseMetadata(@onNull XmlPullParser parser, @NonNull String packageName)1046 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser, 1047 @NonNull String packageName) 1048 throws IOException, XmlPullParserException { 1049 final int outerDepth = parser.getDepth(); 1050 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1051 if (TAG_PACKAGE.equals(parser.getName())) { 1052 return parsePackage(parser, packageName); 1053 } 1054 } 1055 return null; 1056 } 1057 parsePackage(@onNull XmlPullParser parser, @NonNull String packageName)1058 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser, 1059 @NonNull String packageName) 1060 throws IOException, XmlPullParserException { 1061 String label = parser.getAttributeValue(null, ATTR_LABEL); 1062 1063 List<String> outRequestedPermissions = new ArrayList<>(); 1064 List<String> outGrantedPermissions = new ArrayList<>(); 1065 1066 final int outerDepth = parser.getDepth(); 1067 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1068 if (TAG_PERMISSIONS.equals(parser.getName())) { 1069 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions); 1070 } 1071 } 1072 1073 String[] requestedPermissions = new String[outRequestedPermissions.size()]; 1074 outRequestedPermissions.toArray(requestedPermissions); 1075 1076 String[] grantedPermissions = new String[outGrantedPermissions.size()]; 1077 outGrantedPermissions.toArray(grantedPermissions); 1078 1079 return new InstantAppInfo(packageName, label, 1080 requestedPermissions, grantedPermissions); 1081 } 1082 parsePermissions(@onNull XmlPullParser parser, @NonNull List<String> outRequestedPermissions, @NonNull List<String> outGrantedPermissions)1083 private static void parsePermissions(@NonNull XmlPullParser parser, 1084 @NonNull List<String> outRequestedPermissions, 1085 @NonNull List<String> outGrantedPermissions) 1086 throws IOException, XmlPullParserException { 1087 final int outerDepth = parser.getDepth(); 1088 while (XmlUtils.nextElementWithin(parser,outerDepth)) { 1089 if (TAG_PERMISSION.equals(parser.getName())) { 1090 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME); 1091 outRequestedPermissions.add(permission); 1092 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) { 1093 outGrantedPermissions.add(permission); 1094 } 1095 } 1096 } 1097 } 1098 writeUninstalledInstantAppMetadata( @onNull InstantAppInfo instantApp, @UserIdInt int userId)1099 private void writeUninstalledInstantAppMetadata( 1100 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) { 1101 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId); 1102 if (!appDir.exists() && !appDir.mkdirs()) { 1103 return; 1104 } 1105 1106 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE); 1107 1108 AtomicFile destination = new AtomicFile(metadataFile); 1109 FileOutputStream out = null; 1110 try { 1111 out = destination.startWrite(); 1112 1113 XmlSerializer serializer = Xml.newSerializer(); 1114 serializer.setOutput(out, StandardCharsets.UTF_8.name()); 1115 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 1116 1117 serializer.startDocument(null, true); 1118 1119 serializer.startTag(null, TAG_PACKAGE); 1120 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel( 1121 mService.mContext.getPackageManager()).toString()); 1122 1123 serializer.startTag(null, TAG_PERMISSIONS); 1124 for (String permission : instantApp.getRequestedPermissions()) { 1125 serializer.startTag(null, TAG_PERMISSION); 1126 serializer.attribute(null, ATTR_NAME, permission); 1127 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) { 1128 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true)); 1129 } 1130 serializer.endTag(null, TAG_PERMISSION); 1131 } 1132 serializer.endTag(null, TAG_PERMISSIONS); 1133 1134 serializer.endTag(null, TAG_PACKAGE); 1135 1136 serializer.endDocument(); 1137 destination.finishWrite(out); 1138 } catch (Throwable t) { 1139 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t); 1140 destination.failWrite(out); 1141 } finally { 1142 IoUtils.closeQuietly(out); 1143 } 1144 } 1145 getInstantApplicationsDir(int userId)1146 private static @NonNull File getInstantApplicationsDir(int userId) { 1147 return new File(Environment.getUserSystemDirectory(userId), 1148 INSTANT_APPS_FOLDER); 1149 } 1150 getInstantApplicationDir(String packageName, int userId)1151 private static @NonNull File getInstantApplicationDir(String packageName, int userId) { 1152 return new File(getInstantApplicationsDir(userId), packageName); 1153 } 1154 deleteDir(@onNull File dir)1155 private static void deleteDir(@NonNull File dir) { 1156 File[] files = dir.listFiles(); 1157 if (files != null) { 1158 for (File file : files) { 1159 deleteDir(file); 1160 } 1161 } 1162 dir.delete(); 1163 } 1164 1165 private static final class UninstalledInstantAppState { 1166 final InstantAppInfo mInstantAppInfo; 1167 final long mTimestamp; 1168 UninstalledInstantAppState(InstantAppInfo instantApp, long timestamp)1169 public UninstalledInstantAppState(InstantAppInfo instantApp, 1170 long timestamp) { 1171 mInstantAppInfo = instantApp; 1172 mTimestamp = timestamp; 1173 } 1174 } 1175 1176 private final class CookiePersistence extends Handler { 1177 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */ 1178 1179 // The cookies are cached per package name per user-id in this sparse 1180 // array. The caching is so that pending persistence can be canceled within 1181 // a short interval. To ensure we still return pending persist cookies 1182 // for a package that uninstalled and reinstalled while the persistence 1183 // was still pending, we use the package name as a key for 1184 // mPendingPersistCookies, since that stays stable across reinstalls. 1185 private final SparseArray<ArrayMap<String, SomeArgs>> mPendingPersistCookies 1186 = new SparseArray<>(); 1187 CookiePersistence(Looper looper)1188 public CookiePersistence(Looper looper) { 1189 super(looper); 1190 } 1191 schedulePersistLPw(@serIdInt int userId, @NonNull PackageParser.Package pkg, @NonNull byte[] cookie)1192 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg, 1193 @NonNull byte[] cookie) { 1194 // Before we used only the first signature to compute the SHA 256 but some 1195 // apps could be singed by multiple certs and the cert order is undefined. 1196 // We prefer the modern computation procedure where all certs are taken 1197 // into account and delete the file derived via the legacy hash computation. 1198 File newCookieFile = computeInstantCookieFile(pkg.packageName, 1199 PackageUtils.computeSignaturesSha256Digest(pkg.mSigningDetails.signatures), userId); 1200 if (!pkg.mSigningDetails.hasSignatures()) { 1201 Slog.wtf(LOG_TAG, "Parsed Instant App contains no valid signatures!"); 1202 } 1203 File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId); 1204 if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) { 1205 oldCookieFile.delete(); 1206 } 1207 cancelPendingPersistLPw(pkg, userId); 1208 addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile); 1209 sendMessageDelayed(obtainMessage(userId, pkg), 1210 PERSIST_COOKIE_DELAY_MILLIS); 1211 } 1212 getPendingPersistCookieLPr(@onNull PackageParser.Package pkg, @UserIdInt int userId)1213 public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg, 1214 @UserIdInt int userId) { 1215 ArrayMap<String, SomeArgs> pendingWorkForUser = 1216 mPendingPersistCookies.get(userId); 1217 if (pendingWorkForUser != null) { 1218 SomeArgs state = pendingWorkForUser.get(pkg.packageName); 1219 if (state != null) { 1220 return (byte[]) state.arg1; 1221 } 1222 } 1223 return null; 1224 } 1225 cancelPendingPersistLPw(@onNull PackageParser.Package pkg, @UserIdInt int userId)1226 public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg, 1227 @UserIdInt int userId) { 1228 removeMessages(userId, pkg); 1229 SomeArgs state = removePendingPersistCookieLPr(pkg, userId); 1230 if (state != null) { 1231 state.recycle(); 1232 } 1233 } 1234 addPendingPersistCookieLPw(@serIdInt int userId, @NonNull PackageParser.Package pkg, @NonNull byte[] cookie, @NonNull File cookieFile)1235 private void addPendingPersistCookieLPw(@UserIdInt int userId, 1236 @NonNull PackageParser.Package pkg, @NonNull byte[] cookie, 1237 @NonNull File cookieFile) { 1238 ArrayMap<String, SomeArgs> pendingWorkForUser = 1239 mPendingPersistCookies.get(userId); 1240 if (pendingWorkForUser == null) { 1241 pendingWorkForUser = new ArrayMap<>(); 1242 mPendingPersistCookies.put(userId, pendingWorkForUser); 1243 } 1244 SomeArgs args = SomeArgs.obtain(); 1245 args.arg1 = cookie; 1246 args.arg2 = cookieFile; 1247 pendingWorkForUser.put(pkg.packageName, args); 1248 } 1249 removePendingPersistCookieLPr(@onNull PackageParser.Package pkg, @UserIdInt int userId)1250 private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg, 1251 @UserIdInt int userId) { 1252 ArrayMap<String, SomeArgs> pendingWorkForUser = 1253 mPendingPersistCookies.get(userId); 1254 SomeArgs state = null; 1255 if (pendingWorkForUser != null) { 1256 state = pendingWorkForUser.remove(pkg.packageName); 1257 if (pendingWorkForUser.isEmpty()) { 1258 mPendingPersistCookies.remove(userId); 1259 } 1260 } 1261 return state; 1262 } 1263 1264 @Override handleMessage(Message message)1265 public void handleMessage(Message message) { 1266 int userId = message.what; 1267 PackageParser.Package pkg = (PackageParser.Package) message.obj; 1268 SomeArgs state = removePendingPersistCookieLPr(pkg, userId); 1269 if (state == null) { 1270 return; 1271 } 1272 byte[] cookie = (byte[]) state.arg1; 1273 File cookieFile = (File) state.arg2; 1274 state.recycle(); 1275 persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId); 1276 } 1277 } 1278 } 1279