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