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