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