1 /* 2 * Copyright (C) 2016 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 package com.android.server.pm; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.annotation.UserIdInt; 21 import android.app.appsearch.AppSearchManager; 22 import android.app.appsearch.AppSearchSession; 23 import android.content.pm.ShortcutManager; 24 import android.metrics.LogMaker; 25 import android.os.Binder; 26 import android.os.FileUtils; 27 import android.os.UserHandle; 28 import android.text.TextUtils; 29 import android.text.format.Formatter; 30 import android.util.ArrayMap; 31 import android.util.Log; 32 import android.util.Slog; 33 import android.util.TypedXmlPullParser; 34 import android.util.TypedXmlSerializer; 35 36 import com.android.internal.annotations.GuardedBy; 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.internal.infra.AndroidFuture; 39 import com.android.internal.logging.MetricsLogger; 40 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 41 import com.android.server.FgThread; 42 import com.android.server.pm.ShortcutService.DumpFilter; 43 import com.android.server.pm.ShortcutService.InvalidFileFormatException; 44 45 import org.json.JSONArray; 46 import org.json.JSONException; 47 import org.json.JSONObject; 48 import org.xmlpull.v1.XmlPullParser; 49 import org.xmlpull.v1.XmlPullParserException; 50 51 import java.io.File; 52 import java.io.IOException; 53 import java.io.PrintWriter; 54 import java.util.ArrayList; 55 import java.util.Objects; 56 import java.util.concurrent.CompletableFuture; 57 import java.util.concurrent.Executor; 58 import java.util.function.Consumer; 59 60 /** 61 * User information used by {@link ShortcutService}. 62 * 63 * All methods should be guarded by {@code #mService.mLock}. 64 */ 65 class ShortcutUser { 66 private static final String TAG = ShortcutService.TAG; 67 68 static final String DIRECTORY_PACKAGES = "packages"; 69 static final String DIRECTORY_LUANCHERS = "launchers"; 70 71 static final String TAG_ROOT = "user"; 72 private static final String TAG_LAUNCHER = "launcher"; 73 74 private static final String ATTR_VALUE = "value"; 75 private static final String ATTR_KNOWN_LOCALES = "locales"; 76 77 // Suffix "2" was added to force rescan all packages after the next OTA. 78 private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time2"; 79 private static final String ATTR_LAST_APP_SCAN_OS_FINGERPRINT = "last-app-scan-fp"; 80 private static final String ATTR_RESTORE_SOURCE_FINGERPRINT = "restore-from-fp"; 81 private static final String KEY_USER_ID = "userId"; 82 private static final String KEY_LAUNCHERS = "launchers"; 83 private static final String KEY_PACKAGES = "packages"; 84 85 static final class PackageWithUser { 86 final int userId; 87 final String packageName; 88 PackageWithUser(int userId, String packageName)89 private PackageWithUser(int userId, String packageName) { 90 this.userId = userId; 91 this.packageName = Objects.requireNonNull(packageName); 92 } 93 of(int userId, String packageName)94 public static PackageWithUser of(int userId, String packageName) { 95 return new PackageWithUser(userId, packageName); 96 } 97 of(ShortcutPackageItem spi)98 public static PackageWithUser of(ShortcutPackageItem spi) { 99 return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName()); 100 } 101 102 @Override hashCode()103 public int hashCode() { 104 return packageName.hashCode() ^ userId; 105 } 106 107 @Override equals(Object obj)108 public boolean equals(Object obj) { 109 if (!(obj instanceof PackageWithUser)) { 110 return false; 111 } 112 final PackageWithUser that = (PackageWithUser) obj; 113 114 return userId == that.userId && packageName.equals(that.packageName); 115 } 116 117 @Override toString()118 public String toString() { 119 return String.format("[Package: %d, %s]", userId, packageName); 120 } 121 } 122 123 final ShortcutService mService; 124 final AppSearchManager mAppSearchManager; 125 final Executor mExecutor; 126 127 @UserIdInt 128 private final int mUserId; 129 130 private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); 131 132 private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); 133 134 /** In-memory-cached default launcher. */ 135 private String mCachedLauncher; 136 137 private String mKnownLocales; 138 139 private long mLastAppScanTime; 140 141 private String mLastAppScanOsFingerprint; 142 private String mRestoreFromOsFingerprint; 143 144 private final Object mLock = new Object(); 145 146 @GuardedBy("mLock") 147 private final ArrayList<AndroidFuture<AppSearchSession>> mInFlightSessions = new ArrayList<>(); 148 ShortcutUser(ShortcutService service, int userId)149 public ShortcutUser(ShortcutService service, int userId) { 150 mService = service; 151 mUserId = userId; 152 mAppSearchManager = service.mContext.createContextAsUser(UserHandle.of(userId), 0) 153 .getSystemService(AppSearchManager.class); 154 mExecutor = FgThread.getExecutor(); 155 } 156 getUserId()157 public int getUserId() { 158 return mUserId; 159 } 160 getLastAppScanTime()161 public long getLastAppScanTime() { 162 return mLastAppScanTime; 163 } 164 setLastAppScanTime(long lastAppScanTime)165 public void setLastAppScanTime(long lastAppScanTime) { 166 mLastAppScanTime = lastAppScanTime; 167 } 168 getLastAppScanOsFingerprint()169 public String getLastAppScanOsFingerprint() { 170 return mLastAppScanOsFingerprint; 171 } 172 setLastAppScanOsFingerprint(String lastAppScanOsFingerprint)173 public void setLastAppScanOsFingerprint(String lastAppScanOsFingerprint) { 174 mLastAppScanOsFingerprint = lastAppScanOsFingerprint; 175 } 176 177 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 178 // remove from it. 179 @VisibleForTesting getAllPackagesForTest()180 ArrayMap<String, ShortcutPackage> getAllPackagesForTest() { 181 return mPackages; 182 } 183 hasPackage(@onNull String packageName)184 public boolean hasPackage(@NonNull String packageName) { 185 return mPackages.containsKey(packageName); 186 } 187 addPackage(@onNull ShortcutPackage p)188 private void addPackage(@NonNull ShortcutPackage p) { 189 p.replaceUser(this); 190 mPackages.put(p.getPackageName(), p); 191 } 192 removePackage(@onNull String packageName)193 public ShortcutPackage removePackage(@NonNull String packageName) { 194 final ShortcutPackage removed = mPackages.remove(packageName); 195 196 if (removed != null) { 197 removed.removeAllShortcutsAsync(); 198 } 199 mService.cleanupBitmapsForPackage(mUserId, packageName); 200 201 return removed; 202 } 203 204 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 205 // remove from it. 206 @VisibleForTesting getAllLaunchersForTest()207 ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() { 208 return mLaunchers; 209 } 210 addLauncher(ShortcutLauncher launcher)211 private void addLauncher(ShortcutLauncher launcher) { 212 launcher.replaceUser(this); 213 mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), 214 launcher.getPackageName()), launcher); 215 } 216 217 @Nullable removeLauncher( @serIdInt int packageUserId, @NonNull String packageName)218 public ShortcutLauncher removeLauncher( 219 @UserIdInt int packageUserId, @NonNull String packageName) { 220 return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); 221 } 222 223 @Nullable getPackageShortcutsIfExists(@onNull String packageName)224 public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) { 225 final ShortcutPackage ret = mPackages.get(packageName); 226 if (ret != null) { 227 ret.attemptToRestoreIfNeededAndSave(); 228 } 229 return ret; 230 } 231 232 @NonNull getPackageShortcuts(@onNull String packageName)233 public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { 234 ShortcutPackage ret = getPackageShortcutsIfExists(packageName); 235 if (ret == null) { 236 ret = new ShortcutPackage(this, mUserId, packageName); 237 mPackages.put(packageName, ret); 238 } 239 return ret; 240 } 241 242 @NonNull getLauncherShortcuts(@onNull String packageName, @UserIdInt int launcherUserId)243 public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName, 244 @UserIdInt int launcherUserId) { 245 final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); 246 ShortcutLauncher ret = mLaunchers.get(key); 247 if (ret == null) { 248 ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId); 249 mLaunchers.put(key, ret); 250 } else { 251 ret.attemptToRestoreIfNeededAndSave(); 252 } 253 return ret; 254 } 255 forAllPackages(Consumer<? super ShortcutPackage> callback)256 public void forAllPackages(Consumer<? super ShortcutPackage> callback) { 257 final int size = mPackages.size(); 258 for (int i = 0; i < size; i++) { 259 callback.accept(mPackages.valueAt(i)); 260 } 261 } 262 forAllLaunchers(Consumer<? super ShortcutLauncher> callback)263 public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) { 264 final int size = mLaunchers.size(); 265 for (int i = 0; i < size; i++) { 266 callback.accept(mLaunchers.valueAt(i)); 267 } 268 } 269 forAllPackageItems(Consumer<? super ShortcutPackageItem> callback)270 public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) { 271 forAllLaunchers(callback); 272 forAllPackages(callback); 273 } 274 forPackageItem(@onNull String packageName, @UserIdInt int packageUserId, Consumer<ShortcutPackageItem> callback)275 public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, 276 Consumer<ShortcutPackageItem> callback) { 277 forAllPackageItems(spi -> { 278 if ((spi.getPackageUserId() == packageUserId) 279 && spi.getPackageName().equals(packageName)) { 280 callback.accept(spi); 281 } 282 }); 283 } 284 285 /** 286 * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the 287 * information on the package is up-to-date. 288 * 289 * We use broadcasts to handle locale changes and package changes, but because broadcasts 290 * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event 291 * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast. 292 * 293 * So we call this method at all entry points from publishers to make sure we update all 294 * relevant information. 295 * 296 * Similar inconsistencies can happen when the launcher fetches shortcut information, but 297 * that's a less of an issue because for the launcher we report shortcut changes with 298 * callbacks. 299 */ onCalledByPublisher(@onNull String packageName)300 public void onCalledByPublisher(@NonNull String packageName) { 301 detectLocaleChange(); 302 rescanPackageIfNeeded(packageName, /*forceRescan=*/ false); 303 } 304 getKnownLocales()305 private String getKnownLocales() { 306 if (TextUtils.isEmpty(mKnownLocales)) { 307 mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId); 308 mService.scheduleSaveUser(mUserId); 309 } 310 return mKnownLocales; 311 } 312 313 /** 314 * Check to see if the system locale has changed, and if so, reset throttling 315 * and update resource strings. 316 */ detectLocaleChange()317 public void detectLocaleChange() { 318 final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId); 319 if (!TextUtils.isEmpty(mKnownLocales) && mKnownLocales.equals(currentLocales)) { 320 return; 321 } 322 if (ShortcutService.DEBUG) { 323 Slog.d(TAG, "Locale changed from " + mKnownLocales + " to " + currentLocales 324 + " for user " + mUserId); 325 } 326 327 mKnownLocales = currentLocales; 328 329 forAllPackages(pkg -> { 330 pkg.resetRateLimiting(); 331 pkg.resolveResourceStrings(); 332 }); 333 334 mService.scheduleSaveUser(mUserId); 335 } 336 rescanPackageIfNeeded(@onNull String packageName, boolean forceRescan)337 public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) { 338 final boolean isNewApp = !mPackages.containsKey(packageName); 339 if (ShortcutService.DEBUG_REBOOT) { 340 Slog.d(TAG, "rescanPackageIfNeeded " + getUserId() + "@" + packageName 341 + ", forceRescan=" + forceRescan + " , isNewApp=" + isNewApp); 342 } 343 344 final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName); 345 346 if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) { 347 if (isNewApp) { 348 mPackages.remove(packageName); 349 } 350 } 351 } 352 attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, @UserIdInt int packageUserId)353 public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, 354 @UserIdInt int packageUserId) { 355 forPackageItem(packageName, packageUserId, spi -> { 356 spi.attemptToRestoreIfNeededAndSave(); 357 }); 358 } 359 saveToXml(TypedXmlSerializer out, boolean forBackup)360 public void saveToXml(TypedXmlSerializer out, boolean forBackup) 361 throws IOException, XmlPullParserException { 362 out.startTag(null, TAG_ROOT); 363 364 if (!forBackup) { 365 // Don't have to back them up. 366 ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales); 367 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, 368 mLastAppScanTime); 369 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT, 370 mLastAppScanOsFingerprint); 371 ShortcutService.writeAttr(out, ATTR_RESTORE_SOURCE_FINGERPRINT, 372 mRestoreFromOsFingerprint); 373 } else { 374 ShortcutService.writeAttr(out, ATTR_RESTORE_SOURCE_FINGERPRINT, 375 mService.injectBuildFingerprint()); 376 } 377 378 if (!forBackup) { 379 // Since we are not handling package deletion yet, or any single package changes, just 380 // clean the directory and rewrite all the ShortcutPackageItems. 381 final File root = mService.injectUserDataPath(mUserId); 382 FileUtils.deleteContents(new File(root, DIRECTORY_PACKAGES)); 383 FileUtils.deleteContents(new File(root, DIRECTORY_LUANCHERS)); 384 } 385 // Can't use forEachPackageItem due to the checked exceptions. 386 { 387 final int size = mLaunchers.size(); 388 for (int i = 0; i < size; i++) { 389 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup); 390 } 391 } 392 { 393 final int size = mPackages.size(); 394 for (int i = 0; i < size; i++) { 395 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup); 396 } 397 } 398 399 out.endTag(null, TAG_ROOT); 400 } 401 saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi, boolean forBackup)402 private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi, 403 boolean forBackup) throws IOException, XmlPullParserException { 404 spi.waitForBitmapSaves(); 405 if (forBackup) { 406 if (spi.getPackageUserId() != spi.getOwnerUserId()) { 407 return; // Don't save cross-user information. 408 } 409 spi.saveToXml(out, forBackup); 410 } else { 411 spi.saveShortcutPackageItem(); 412 } 413 } 414 loadFromXml(ShortcutService s, TypedXmlPullParser parser, int userId, boolean fromBackup)415 public static ShortcutUser loadFromXml(ShortcutService s, TypedXmlPullParser parser, int userId, 416 boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException { 417 final ShortcutUser ret = new ShortcutUser(s, userId); 418 boolean readShortcutItems = false; 419 try { 420 ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, 421 ATTR_KNOWN_LOCALES); 422 423 // If lastAppScanTime is in the future, that means the clock went backwards. 424 // Just scan all apps again. 425 final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, 426 ATTR_LAST_APP_SCAN_TIME); 427 final long currentTime = s.injectCurrentTimeMillis(); 428 ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; 429 ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser, 430 ATTR_LAST_APP_SCAN_OS_FINGERPRINT); 431 ret.mRestoreFromOsFingerprint = ShortcutService.parseStringAttribute(parser, 432 ATTR_RESTORE_SOURCE_FINGERPRINT); 433 final int outerDepth = parser.getDepth(); 434 int type; 435 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 436 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 437 if (type != XmlPullParser.START_TAG) { 438 continue; 439 } 440 final int depth = parser.getDepth(); 441 final String tag = parser.getName(); 442 443 if (depth == outerDepth + 1) { 444 switch (tag) { 445 case ShortcutPackage.TAG_ROOT: { 446 final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( 447 s, ret, parser, fromBackup); 448 449 // Don't use addShortcut(), we don't need to save the icon. 450 ret.mPackages.put(shortcuts.getPackageName(), shortcuts); 451 readShortcutItems = true; 452 continue; 453 } 454 455 case ShortcutLauncher.TAG_ROOT: { 456 ret.addLauncher( 457 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); 458 readShortcutItems = true; 459 continue; 460 } 461 } 462 } 463 ShortcutService.warnForInvalidTag(depth, tag); 464 } 465 } catch (RuntimeException e) { 466 throw new ShortcutService.InvalidFileFormatException( 467 "Unable to parse file", e); 468 } 469 470 if (readShortcutItems) { 471 // If the shortcuts info was read from the main Xml, skip reading from individual files. 472 // Data will get stored in the new format during the next call to saveToXml(). 473 // TODO: ret.forAllPackageItems((ShortcutPackageItem item) -> item.markDirty()); 474 s.scheduleSaveUser(userId); 475 } else { 476 final File root = s.injectUserDataPath(userId); 477 478 forAllFilesIn(new File(root, DIRECTORY_PACKAGES), (File f) -> { 479 final ShortcutPackage sp = ShortcutPackage.loadFromFile(s, ret, f, fromBackup); 480 if (sp != null) { 481 ret.mPackages.put(sp.getPackageName(), sp); 482 } 483 }); 484 485 forAllFilesIn(new File(root, DIRECTORY_LUANCHERS), (File f) -> { 486 final ShortcutLauncher sl = 487 ShortcutLauncher.loadFromFile(f, ret, userId, fromBackup); 488 if (sl != null) { 489 ret.addLauncher(sl); 490 } 491 }); 492 } 493 494 return ret; 495 } 496 forAllFilesIn(File path, Consumer<File> callback)497 private static void forAllFilesIn(File path, Consumer<File> callback) { 498 if (!path.exists()) { 499 return; 500 } 501 File[] list = path.listFiles(); 502 for (File f : list) { 503 callback.accept(f); 504 } 505 } 506 setCachedLauncher(String launcher)507 public void setCachedLauncher(String launcher) { 508 mCachedLauncher = launcher; 509 } 510 getCachedLauncher()511 public String getCachedLauncher() { 512 return mCachedLauncher; 513 } 514 resetThrottling()515 public void resetThrottling() { 516 for (int i = mPackages.size() - 1; i >= 0; i--) { 517 mPackages.valueAt(i).resetThrottling(); 518 } 519 } 520 mergeRestoredFile(ShortcutUser restored)521 public void mergeRestoredFile(ShortcutUser restored) { 522 final ShortcutService s = mService; 523 // Note, a restore happens only at the end of setup wizard. At this point, no apps are 524 // installed from Play Store yet, but it's still possible that system apps have already 525 // published dynamic shortcuts, since some apps do so on BOOT_COMPLETED. 526 // When such a system app has allowbackup=true, then we go ahead and replace all existing 527 // shortcuts with the restored shortcuts. (Then we'll re-publish manifest shortcuts later 528 // in the call site.) 529 // When such a system app has allowbackup=false, then we'll keep the shortcuts that have 530 // already been published. So we selectively add restored ShortcutPackages here. 531 // 532 // The same logic applies to launchers, but since launchers shouldn't pin shortcuts 533 // without users interaction it's really not a big deal, so we just clear existing 534 // ShortcutLauncher instances in mLaunchers and add all the restored ones here. 535 536 int[] restoredLaunchers = new int[1]; 537 int[] restoredPackages = new int[1]; 538 int[] restoredShortcuts = new int[1]; 539 540 mLaunchers.clear(); 541 restored.forAllLaunchers(sl -> { 542 // If the app is already installed and allowbackup = false, then ignore the restored 543 // data. 544 if (s.isPackageInstalled(sl.getPackageName(), getUserId()) 545 && !s.shouldBackupApp(sl.getPackageName(), getUserId())) { 546 return; 547 } 548 addLauncher(sl); 549 restoredLaunchers[0]++; 550 }); 551 restored.forAllPackages(sp -> { 552 // If the app is already installed and allowbackup = false, then ignore the restored 553 // data. 554 if (s.isPackageInstalled(sp.getPackageName(), getUserId()) 555 && !s.shouldBackupApp(sp.getPackageName(), getUserId())) { 556 return; 557 } 558 559 final ShortcutPackage previous = getPackageShortcutsIfExists(sp.getPackageName()); 560 if (previous != null && previous.hasNonManifestShortcuts()) { 561 Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored." 562 + " Existing non-manifeset shortcuts will be overwritten."); 563 } 564 sp.removeAllShortcutsAsync(); 565 addPackage(sp); 566 restoredPackages[0]++; 567 restoredShortcuts[0] += sp.getShortcutCount(); 568 }); 569 // Empty the launchers and packages in restored to avoid accidentally using them. 570 restored.mLaunchers.clear(); 571 restored.mPackages.clear(); 572 573 mRestoreFromOsFingerprint = restored.mRestoreFromOsFingerprint; 574 575 Slog.i(TAG, "Restored: L=" + restoredLaunchers[0] 576 + " P=" + restoredPackages[0] 577 + " S=" + restoredShortcuts[0]); 578 } 579 dump(@onNull PrintWriter pw, @NonNull String prefix, DumpFilter filter)580 public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) { 581 if (filter.shouldDumpDetails()) { 582 pw.print(prefix); 583 pw.print("User: "); 584 pw.print(mUserId); 585 pw.print(" Known locales: "); 586 pw.print(mKnownLocales); 587 pw.print(" Last app scan: ["); 588 pw.print(mLastAppScanTime); 589 pw.print("] "); 590 pw.println(ShortcutService.formatTime(mLastAppScanTime)); 591 592 prefix += prefix + " "; 593 594 pw.print(prefix); 595 pw.print("Last app scan FP: "); 596 pw.println(mLastAppScanOsFingerprint); 597 598 pw.print(prefix); 599 pw.print("Restore from FP: "); 600 pw.print(mRestoreFromOsFingerprint); 601 pw.println(); 602 603 pw.print(prefix); 604 pw.print("Cached launcher: "); 605 pw.print(mCachedLauncher); 606 pw.println(); 607 } 608 609 for (int i = 0; i < mLaunchers.size(); i++) { 610 ShortcutLauncher launcher = mLaunchers.valueAt(i); 611 if (filter.isPackageMatch(launcher.getPackageName())) { 612 launcher.dump(pw, prefix, filter); 613 } 614 } 615 616 for (int i = 0; i < mPackages.size(); i++) { 617 ShortcutPackage pkg = mPackages.valueAt(i); 618 if (filter.isPackageMatch(pkg.getPackageName())) { 619 pkg.dump(pw, prefix, filter); 620 } 621 } 622 623 if (filter.shouldDumpDetails()) { 624 pw.println(); 625 pw.print(prefix); 626 pw.println("Bitmap directories: "); 627 dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId)); 628 } 629 } 630 dumpDirectorySize(@onNull PrintWriter pw, @NonNull String prefix, File path)631 private void dumpDirectorySize(@NonNull PrintWriter pw, 632 @NonNull String prefix, File path) { 633 int numFiles = 0; 634 long size = 0; 635 final File[] children = path.listFiles(); 636 if (children != null) { 637 for (File child : path.listFiles()) { 638 if (child.isFile()) { 639 numFiles++; 640 size += child.length(); 641 } else if (child.isDirectory()) { 642 dumpDirectorySize(pw, prefix + " ", child); 643 } 644 } 645 } 646 pw.print(prefix); 647 pw.print("Path: "); 648 pw.print(path.getName()); 649 pw.print("/ has "); 650 pw.print(numFiles); 651 pw.print(" files, size="); 652 pw.print(size); 653 pw.print(" ("); 654 pw.print(Formatter.formatFileSize(mService.mContext, size)); 655 pw.println(")"); 656 } 657 dumpCheckin(boolean clear)658 public JSONObject dumpCheckin(boolean clear) throws JSONException { 659 final JSONObject result = new JSONObject(); 660 661 result.put(KEY_USER_ID, mUserId); 662 663 { 664 final JSONArray launchers = new JSONArray(); 665 for (int i = 0; i < mLaunchers.size(); i++) { 666 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear)); 667 } 668 result.put(KEY_LAUNCHERS, launchers); 669 } 670 671 { 672 final JSONArray packages = new JSONArray(); 673 for (int i = 0; i < mPackages.size(); i++) { 674 packages.put(mPackages.valueAt(i).dumpCheckin(clear)); 675 } 676 result.put(KEY_PACKAGES, packages); 677 } 678 679 return result; 680 } 681 logSharingShortcutStats(MetricsLogger logger)682 void logSharingShortcutStats(MetricsLogger logger) { 683 int packageWithShareTargetsCount = 0; 684 int totalSharingShortcutCount = 0; 685 for (int i = 0; i < mPackages.size(); i++) { 686 if (mPackages.valueAt(i).hasShareTargets()) { 687 packageWithShareTargetsCount++; 688 totalSharingShortcutCount += mPackages.valueAt(i).getSharingShortcutCount(); 689 } 690 } 691 692 final LogMaker logMaker = new LogMaker(MetricsEvent.ACTION_SHORTCUTS_CHANGED); 693 logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_USER_ID) 694 .setSubtype(mUserId)); 695 logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_PACKAGE_COUNT) 696 .setSubtype(packageWithShareTargetsCount)); 697 logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_SHORTCUT_COUNT) 698 .setSubtype(totalSharingShortcutCount)); 699 } 700 701 @NonNull getAppSearch( @onNull final AppSearchManager.SearchContext searchContext)702 AndroidFuture<AppSearchSession> getAppSearch( 703 @NonNull final AppSearchManager.SearchContext searchContext) { 704 final AndroidFuture<AppSearchSession> future = new AndroidFuture<>(); 705 synchronized (mLock) { 706 mInFlightSessions.removeIf(CompletableFuture::isDone); 707 mInFlightSessions.add(future); 708 } 709 if (mAppSearchManager == null) { 710 future.completeExceptionally(new RuntimeException("app search manager is null")); 711 return future; 712 } 713 if (!mService.mUserManagerInternal.isUserUnlockingOrUnlocked(getUserId())) { 714 // In rare cases the user might be stopped immediate after it started, in these cases 715 // any on-going session will need to be abandoned. 716 future.completeExceptionally(new RuntimeException("User " + getUserId() + " is ")); 717 return future; 718 } 719 final long callingIdentity = Binder.clearCallingIdentity(); 720 try { 721 mAppSearchManager.createSearchSession(searchContext, mExecutor, result -> { 722 if (!result.isSuccess()) { 723 future.completeExceptionally( 724 new RuntimeException(result.getErrorMessage())); 725 return; 726 } 727 future.complete(result.getResultValue()); 728 }); 729 } finally { 730 Binder.restoreCallingIdentity(callingIdentity); 731 } 732 return future; 733 } 734 cancelAllInFlightTasks()735 void cancelAllInFlightTasks() { 736 synchronized (mLock) { 737 for (AndroidFuture<AppSearchSession> session : mInFlightSessions) { 738 session.cancel(true); 739 } 740 mInFlightSessions.clear(); 741 } 742 } 743 } 744