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