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.text.TextUtils; 24 import android.text.format.Formatter; 25 import android.util.ArrayMap; 26 import android.util.Log; 27 import android.util.Slog; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.util.Preconditions; 31 import com.android.server.pm.ShortcutService.DumpFilter; 32 import com.android.server.pm.ShortcutService.InvalidFileFormatException; 33 34 import org.json.JSONArray; 35 import org.json.JSONException; 36 import org.json.JSONObject; 37 import org.xmlpull.v1.XmlPullParser; 38 import org.xmlpull.v1.XmlPullParserException; 39 import org.xmlpull.v1.XmlSerializer; 40 41 import java.io.File; 42 import java.io.IOException; 43 import java.io.PrintWriter; 44 import java.util.Objects; 45 import java.util.function.Consumer; 46 47 /** 48 * User information used by {@link ShortcutService}. 49 * 50 * All methods should be guarded by {@code #mService.mLock}. 51 */ 52 class ShortcutUser { 53 private static final String TAG = ShortcutService.TAG; 54 55 static final String TAG_ROOT = "user"; 56 private static final String TAG_LAUNCHER = "launcher"; 57 58 private static final String ATTR_VALUE = "value"; 59 private static final String ATTR_KNOWN_LOCALES = "locales"; 60 61 // Suffix "2" was added to force rescan all packages after the next OTA. 62 private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time2"; 63 private static final String ATTR_LAST_APP_SCAN_OS_FINGERPRINT = "last-app-scan-fp"; 64 private static final String ATTR_RESTORE_SOURCE_FINGERPRINT = "restore-from-fp"; 65 private static final String KEY_USER_ID = "userId"; 66 private static final String KEY_LAUNCHERS = "launchers"; 67 private static final String KEY_PACKAGES = "packages"; 68 69 static final class PackageWithUser { 70 final int userId; 71 final String packageName; 72 PackageWithUser(int userId, String packageName)73 private PackageWithUser(int userId, String packageName) { 74 this.userId = userId; 75 this.packageName = Preconditions.checkNotNull(packageName); 76 } 77 of(int userId, String packageName)78 public static PackageWithUser of(int userId, String packageName) { 79 return new PackageWithUser(userId, packageName); 80 } 81 of(ShortcutPackageItem spi)82 public static PackageWithUser of(ShortcutPackageItem spi) { 83 return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName()); 84 } 85 86 @Override hashCode()87 public int hashCode() { 88 return packageName.hashCode() ^ userId; 89 } 90 91 @Override equals(Object obj)92 public boolean equals(Object obj) { 93 if (!(obj instanceof PackageWithUser)) { 94 return false; 95 } 96 final PackageWithUser that = (PackageWithUser) obj; 97 98 return userId == that.userId && packageName.equals(that.packageName); 99 } 100 101 @Override toString()102 public String toString() { 103 return String.format("[Package: %d, %s]", userId, packageName); 104 } 105 } 106 107 final ShortcutService mService; 108 109 @UserIdInt 110 private final int mUserId; 111 112 private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); 113 114 private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); 115 116 /** 117 * Last known launcher. It's used when the default launcher isn't set in PM -- i.e. 118 * when getHomeActivitiesAsUser() return null. We need it so that in this situation the 119 * previously default launcher can still access shortcuts. 120 */ 121 private ComponentName mLastKnownLauncher; 122 123 /** In-memory-cached default launcher. */ 124 private ComponentName mCachedLauncher; 125 126 private String mKnownLocales; 127 128 private long mLastAppScanTime; 129 130 private String mLastAppScanOsFingerprint; 131 private String mRestoreFromOsFingerprint; 132 ShortcutUser(ShortcutService service, int userId)133 public ShortcutUser(ShortcutService service, int userId) { 134 mService = service; 135 mUserId = userId; 136 } 137 getUserId()138 public int getUserId() { 139 return mUserId; 140 } 141 getLastAppScanTime()142 public long getLastAppScanTime() { 143 return mLastAppScanTime; 144 } 145 setLastAppScanTime(long lastAppScanTime)146 public void setLastAppScanTime(long lastAppScanTime) { 147 mLastAppScanTime = lastAppScanTime; 148 } 149 getLastAppScanOsFingerprint()150 public String getLastAppScanOsFingerprint() { 151 return mLastAppScanOsFingerprint; 152 } 153 setLastAppScanOsFingerprint(String lastAppScanOsFingerprint)154 public void setLastAppScanOsFingerprint(String lastAppScanOsFingerprint) { 155 mLastAppScanOsFingerprint = lastAppScanOsFingerprint; 156 } 157 158 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 159 // remove from it. 160 @VisibleForTesting getAllPackagesForTest()161 ArrayMap<String, ShortcutPackage> getAllPackagesForTest() { 162 return mPackages; 163 } 164 hasPackage(@onNull String packageName)165 public boolean hasPackage(@NonNull String packageName) { 166 return mPackages.containsKey(packageName); 167 } 168 addPackage(@onNull ShortcutPackage p)169 private void addPackage(@NonNull ShortcutPackage p) { 170 p.replaceUser(this); 171 mPackages.put(p.getPackageName(), p); 172 } 173 removePackage(@onNull String packageName)174 public ShortcutPackage removePackage(@NonNull String packageName) { 175 final ShortcutPackage removed = mPackages.remove(packageName); 176 177 mService.cleanupBitmapsForPackage(mUserId, packageName); 178 179 return removed; 180 } 181 182 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 183 // remove from it. 184 @VisibleForTesting getAllLaunchersForTest()185 ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() { 186 return mLaunchers; 187 } 188 addLauncher(ShortcutLauncher launcher)189 private void addLauncher(ShortcutLauncher launcher) { 190 launcher.replaceUser(this); 191 mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), 192 launcher.getPackageName()), launcher); 193 } 194 195 @Nullable removeLauncher( @serIdInt int packageUserId, @NonNull String packageName)196 public ShortcutLauncher removeLauncher( 197 @UserIdInt int packageUserId, @NonNull String packageName) { 198 return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); 199 } 200 201 @Nullable getPackageShortcutsIfExists(@onNull String packageName)202 public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) { 203 final ShortcutPackage ret = mPackages.get(packageName); 204 if (ret != null) { 205 ret.attemptToRestoreIfNeededAndSave(); 206 } 207 return ret; 208 } 209 210 @NonNull getPackageShortcuts(@onNull String packageName)211 public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { 212 ShortcutPackage ret = getPackageShortcutsIfExists(packageName); 213 if (ret == null) { 214 ret = new ShortcutPackage(this, mUserId, packageName); 215 mPackages.put(packageName, ret); 216 } 217 return ret; 218 } 219 220 @NonNull getLauncherShortcuts(@onNull String packageName, @UserIdInt int launcherUserId)221 public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName, 222 @UserIdInt int launcherUserId) { 223 final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); 224 ShortcutLauncher ret = mLaunchers.get(key); 225 if (ret == null) { 226 ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId); 227 mLaunchers.put(key, ret); 228 } else { 229 ret.attemptToRestoreIfNeededAndSave(); 230 } 231 return ret; 232 } 233 forAllPackages(Consumer<? super ShortcutPackage> callback)234 public void forAllPackages(Consumer<? super ShortcutPackage> callback) { 235 final int size = mPackages.size(); 236 for (int i = 0; i < size; i++) { 237 callback.accept(mPackages.valueAt(i)); 238 } 239 } 240 forAllLaunchers(Consumer<? super ShortcutLauncher> callback)241 public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) { 242 final int size = mLaunchers.size(); 243 for (int i = 0; i < size; i++) { 244 callback.accept(mLaunchers.valueAt(i)); 245 } 246 } 247 forAllPackageItems(Consumer<? super ShortcutPackageItem> callback)248 public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) { 249 forAllLaunchers(callback); 250 forAllPackages(callback); 251 } 252 forPackageItem(@onNull String packageName, @UserIdInt int packageUserId, Consumer<ShortcutPackageItem> callback)253 public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, 254 Consumer<ShortcutPackageItem> callback) { 255 forAllPackageItems(spi -> { 256 if ((spi.getPackageUserId() == packageUserId) 257 && spi.getPackageName().equals(packageName)) { 258 callback.accept(spi); 259 } 260 }); 261 } 262 263 /** 264 * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the 265 * information on the package is up-to-date. 266 * 267 * We use broadcasts to handle locale changes and package changes, but because broadcasts 268 * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event 269 * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast. 270 * 271 * So we call this method at all entry points from publishers to make sure we update all 272 * relevant information. 273 * 274 * Similar inconsistencies can happen when the launcher fetches shortcut information, but 275 * that's a less of an issue because for the launcher we report shortcut changes with 276 * callbacks. 277 */ onCalledByPublisher(@onNull String packageName)278 public void onCalledByPublisher(@NonNull String packageName) { 279 detectLocaleChange(); 280 rescanPackageIfNeeded(packageName, /*forceRescan=*/ false); 281 } 282 getKnownLocales()283 private String getKnownLocales() { 284 if (TextUtils.isEmpty(mKnownLocales)) { 285 mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId); 286 mService.scheduleSaveUser(mUserId); 287 } 288 return mKnownLocales; 289 } 290 291 /** 292 * Check to see if the system locale has changed, and if so, reset throttling 293 * and update resource strings. 294 */ detectLocaleChange()295 public void detectLocaleChange() { 296 final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId); 297 if (getKnownLocales().equals(currentLocales)) { 298 return; 299 } 300 if (ShortcutService.DEBUG) { 301 Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales 302 + " for user " + mUserId); 303 } 304 mKnownLocales = currentLocales; 305 306 forAllPackages(pkg -> { 307 pkg.resetRateLimiting(); 308 pkg.resolveResourceStrings(); 309 }); 310 311 mService.scheduleSaveUser(mUserId); 312 } 313 rescanPackageIfNeeded(@onNull String packageName, boolean forceRescan)314 public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) { 315 final boolean isNewApp = !mPackages.containsKey(packageName); 316 317 final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName); 318 319 if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) { 320 if (isNewApp) { 321 mPackages.remove(packageName); 322 } 323 } 324 } 325 attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, @UserIdInt int packageUserId)326 public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, 327 @UserIdInt int packageUserId) { 328 forPackageItem(packageName, packageUserId, spi -> { 329 spi.attemptToRestoreIfNeededAndSave(); 330 }); 331 } 332 saveToXml(XmlSerializer out, boolean forBackup)333 public void saveToXml(XmlSerializer out, boolean forBackup) 334 throws IOException, XmlPullParserException { 335 out.startTag(null, TAG_ROOT); 336 337 if (!forBackup) { 338 // Don't have to back them up. 339 ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales); 340 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, 341 mLastAppScanTime); 342 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT, 343 mLastAppScanOsFingerprint); 344 ShortcutService.writeAttr(out, ATTR_RESTORE_SOURCE_FINGERPRINT, 345 mRestoreFromOsFingerprint); 346 347 ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher); 348 } else { 349 ShortcutService.writeAttr(out, ATTR_RESTORE_SOURCE_FINGERPRINT, 350 mService.injectBuildFingerprint()); 351 } 352 353 // Can't use forEachPackageItem due to the checked exceptions. 354 { 355 final int size = mLaunchers.size(); 356 for (int i = 0; i < size; i++) { 357 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup); 358 } 359 } 360 { 361 final int size = mPackages.size(); 362 for (int i = 0; i < size; i++) { 363 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup); 364 } 365 } 366 367 out.endTag(null, TAG_ROOT); 368 } 369 saveShortcutPackageItem(XmlSerializer out, ShortcutPackageItem spi, boolean forBackup)370 private void saveShortcutPackageItem(XmlSerializer out, 371 ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { 372 if (forBackup) { 373 if (spi.getPackageUserId() != spi.getOwnerUserId()) { 374 return; // Don't save cross-user information. 375 } 376 } 377 spi.saveToXml(out, forBackup); 378 } 379 loadFromXml(ShortcutService s, XmlPullParser parser, int userId, boolean fromBackup)380 public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, 381 boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException { 382 final ShortcutUser ret = new ShortcutUser(s, userId); 383 384 try { 385 ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, 386 ATTR_KNOWN_LOCALES); 387 388 // If lastAppScanTime is in the future, that means the clock went backwards. 389 // Just scan all apps again. 390 final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, 391 ATTR_LAST_APP_SCAN_TIME); 392 final long currentTime = s.injectCurrentTimeMillis(); 393 ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; 394 ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser, 395 ATTR_LAST_APP_SCAN_OS_FINGERPRINT); 396 ret.mRestoreFromOsFingerprint = ShortcutService.parseStringAttribute(parser, 397 ATTR_RESTORE_SOURCE_FINGERPRINT); 398 final int outerDepth = parser.getDepth(); 399 int type; 400 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 401 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 402 if (type != XmlPullParser.START_TAG) { 403 continue; 404 } 405 final int depth = parser.getDepth(); 406 final String tag = parser.getName(); 407 408 if (depth == outerDepth + 1) { 409 switch (tag) { 410 case TAG_LAUNCHER: { 411 ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute( 412 parser, ATTR_VALUE); 413 continue; 414 } 415 case ShortcutPackage.TAG_ROOT: { 416 final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( 417 s, ret, parser, fromBackup); 418 419 // Don't use addShortcut(), we don't need to save the icon. 420 ret.mPackages.put(shortcuts.getPackageName(), shortcuts); 421 continue; 422 } 423 424 case ShortcutLauncher.TAG_ROOT: { 425 ret.addLauncher( 426 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); 427 continue; 428 } 429 } 430 } 431 ShortcutService.warnForInvalidTag(depth, tag); 432 } 433 } catch (RuntimeException e) { 434 throw new ShortcutService.InvalidFileFormatException( 435 "Unable to parse file", e); 436 } 437 return ret; 438 } 439 getLastKnownLauncher()440 public ComponentName getLastKnownLauncher() { 441 return mLastKnownLauncher; 442 } 443 setLauncher(ComponentName launcherComponent)444 public void setLauncher(ComponentName launcherComponent) { 445 setLauncher(launcherComponent, /* allowPurgeLastKnown */ false); 446 } 447 448 /** Clears the launcher information without clearing the last known one */ clearLauncher()449 public void clearLauncher() { 450 setLauncher(null); 451 } 452 453 /** 454 * Clears the launcher information *with(* clearing the last known one; we do this witl 455 * "cmd shortcut clear-default-launcher". 456 */ forceClearLauncher()457 public void forceClearLauncher() { 458 setLauncher(null, /* allowPurgeLastKnown */ true); 459 } 460 setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown)461 private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) { 462 mCachedLauncher = launcherComponent; // Always update the in-memory cache. 463 464 if (Objects.equals(mLastKnownLauncher, launcherComponent)) { 465 return; 466 } 467 if (!allowPurgeLastKnown && launcherComponent == null) { 468 return; 469 } 470 mLastKnownLauncher = launcherComponent; 471 mService.scheduleSaveUser(mUserId); 472 } 473 getCachedLauncher()474 public ComponentName 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 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 566 pw.print(prefix); 567 pw.print("Cached launcher: "); 568 pw.print(mCachedLauncher); 569 pw.println(); 570 571 pw.print(prefix); 572 pw.print("Last known launcher: "); 573 pw.print(mLastKnownLauncher); 574 pw.println(); 575 } 576 577 for (int i = 0; i < mLaunchers.size(); i++) { 578 ShortcutLauncher launcher = mLaunchers.valueAt(i); 579 if (filter.isPackageMatch(launcher.getPackageName())) { 580 launcher.dump(pw, prefix, filter); 581 } 582 } 583 584 for (int i = 0; i < mPackages.size(); i++) { 585 ShortcutPackage pkg = mPackages.valueAt(i); 586 if (filter.isPackageMatch(pkg.getPackageName())) { 587 pkg.dump(pw, prefix, filter); 588 } 589 } 590 591 if (filter.shouldDumpDetails()) { 592 pw.println(); 593 pw.print(prefix); 594 pw.println("Bitmap directories: "); 595 dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId)); 596 } 597 } 598 dumpDirectorySize(@onNull PrintWriter pw, @NonNull String prefix, File path)599 private void dumpDirectorySize(@NonNull PrintWriter pw, 600 @NonNull String prefix, File path) { 601 int numFiles = 0; 602 long size = 0; 603 final File[] children = path.listFiles(); 604 if (children != null) { 605 for (File child : path.listFiles()) { 606 if (child.isFile()) { 607 numFiles++; 608 size += child.length(); 609 } else if (child.isDirectory()) { 610 dumpDirectorySize(pw, prefix + " ", child); 611 } 612 } 613 } 614 pw.print(prefix); 615 pw.print("Path: "); 616 pw.print(path.getName()); 617 pw.print("/ has "); 618 pw.print(numFiles); 619 pw.print(" files, size="); 620 pw.print(size); 621 pw.print(" ("); 622 pw.print(Formatter.formatFileSize(mService.mContext, size)); 623 pw.println(")"); 624 } 625 dumpCheckin(boolean clear)626 public JSONObject dumpCheckin(boolean clear) throws JSONException { 627 final JSONObject result = new JSONObject(); 628 629 result.put(KEY_USER_ID, mUserId); 630 631 { 632 final JSONArray launchers = new JSONArray(); 633 for (int i = 0; i < mLaunchers.size(); i++) { 634 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear)); 635 } 636 result.put(KEY_LAUNCHERS, launchers); 637 } 638 639 { 640 final JSONArray packages = new JSONArray(); 641 for (int i = 0; i < mPackages.size(); i++) { 642 packages.put(mPackages.valueAt(i).dumpCheckin(clear)); 643 } 644 result.put(KEY_PACKAGES, packages); 645 } 646 647 return result; 648 } 649 } 650