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