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