Home | History | Annotate | Download | only in pm
      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 
     73         private PackageWithUser(int userId, String packageName) {
     74             this.userId = userId;
     75             this.packageName = Preconditions.checkNotNull(packageName);
     76         }
     77 
     78         public static PackageWithUser of(int userId, String packageName) {
     79             return new PackageWithUser(userId, packageName);
     80         }
     81 
     82         public static PackageWithUser of(ShortcutPackageItem spi) {
     83             return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
     84         }
     85 
     86         @Override
     87         public int hashCode() {
     88             return packageName.hashCode() ^ userId;
     89         }
     90 
     91         @Override
     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
    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 
    133     public ShortcutUser(ShortcutService service, int userId) {
    134         mService = service;
    135         mUserId = userId;
    136     }
    137 
    138     public int getUserId() {
    139         return mUserId;
    140     }
    141 
    142     public long getLastAppScanTime() {
    143         return mLastAppScanTime;
    144     }
    145 
    146     public void setLastAppScanTime(long lastAppScanTime) {
    147         mLastAppScanTime = lastAppScanTime;
    148     }
    149 
    150     public String getLastAppScanOsFingerprint() {
    151         return mLastAppScanOsFingerprint;
    152     }
    153 
    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
    161     ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
    162         return mPackages;
    163     }
    164 
    165     public boolean hasPackage(@NonNull String packageName) {
    166         return mPackages.containsKey(packageName);
    167     }
    168 
    169     private void addPackage(@NonNull ShortcutPackage p) {
    170         p.replaceUser(this);
    171         mPackages.put(p.getPackageName(), p);
    172     }
    173 
    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
    185     ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
    186         return mLaunchers;
    187     }
    188 
    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
    196     public ShortcutLauncher removeLauncher(
    197             @UserIdInt int packageUserId, @NonNull String packageName) {
    198         return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
    199     }
    200 
    201     @Nullable
    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
    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
    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 
    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 
    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 
    248     public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
    249         forAllLaunchers(callback);
    250         forAllPackages(callback);
    251     }
    252 
    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      */
    278     public void onCalledByPublisher(@NonNull String packageName) {
    279         detectLocaleChange();
    280         rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
    281     }
    282 
    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      */
    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 
    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 
    326     public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
    327             @UserIdInt int packageUserId) {
    328         forPackageItem(packageName, packageUserId, spi -> {
    329             spi.attemptToRestoreIfNeededAndSave();
    330         });
    331     }
    332 
    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 
    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 
    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 
    440     public ComponentName getLastKnownLauncher() {
    441         return mLastKnownLauncher;
    442     }
    443 
    444     public void setLauncher(ComponentName launcherComponent) {
    445         setLauncher(launcherComponent, /* allowPurgeLastKnown */ false);
    446     }
    447 
    448     /** Clears the launcher information without clearing the last known one */
    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      */
    457     public void forceClearLauncher() {
    458         setLauncher(null, /* allowPurgeLastKnown */ true);
    459     }
    460 
    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 
    474     public ComponentName getCachedLauncher() {
    475         return mCachedLauncher;
    476     }
    477 
    478     public void resetThrottling() {
    479         for (int i = mPackages.size() - 1; i >= 0; i--) {
    480             mPackages.valueAt(i).resetThrottling();
    481         }
    482     }
    483 
    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 
    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 
    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 
    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