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