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