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