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         }
213         ret.attemptToRestoreIfNeededAndSave();
214         return ret;
215     }
216 
forAllPackages(Consumer<? super ShortcutPackage> callback)217     public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
218         final int size = mPackages.size();
219         for (int i = 0; i < size; i++) {
220             callback.accept(mPackages.valueAt(i));
221         }
222     }
223 
forAllLaunchers(Consumer<? super ShortcutLauncher> callback)224     public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
225         final int size = mLaunchers.size();
226         for (int i = 0; i < size; i++) {
227             callback.accept(mLaunchers.valueAt(i));
228         }
229     }
230 
forAllPackageItems(Consumer<? super ShortcutPackageItem> callback)231     public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
232         forAllLaunchers(callback);
233         forAllPackages(callback);
234     }
235 
forPackageItem(@onNull String packageName, @UserIdInt int packageUserId, Consumer<ShortcutPackageItem> callback)236     public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
237             Consumer<ShortcutPackageItem> callback) {
238         forAllPackageItems(spi -> {
239             if ((spi.getPackageUserId() == packageUserId)
240                     && spi.getPackageName().equals(packageName)) {
241                 callback.accept(spi);
242             }
243         });
244     }
245 
246     /**
247      * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the
248      * information on the package is up-to-date.
249      *
250      * We use broadcasts to handle locale changes and package changes, but because broadcasts
251      * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event
252      * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast.
253      *
254      * So we call this method at all entry points from publishers to make sure we update all
255      * relevant information.
256      *
257      * Similar inconsistencies can happen when the launcher fetches shortcut information, but
258      * that's a less of an issue because for the launcher we report shortcut changes with
259      * callbacks.
260      */
onCalledByPublisher(@onNull String packageName)261     public void onCalledByPublisher(@NonNull String packageName) {
262         detectLocaleChange();
263         rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
264     }
265 
getKnownLocales()266     private String getKnownLocales() {
267         if (TextUtils.isEmpty(mKnownLocales)) {
268             mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId);
269             mService.scheduleSaveUser(mUserId);
270         }
271         return mKnownLocales;
272     }
273 
274     /**
275      * Check to see if the system locale has changed, and if so, reset throttling
276      * and update resource strings.
277      */
detectLocaleChange()278     public void detectLocaleChange() {
279         final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId);
280         if (!TextUtils.isEmpty(mKnownLocales) && mKnownLocales.equals(currentLocales)) {
281             return;
282         }
283         if (ShortcutService.DEBUG) {
284             Slog.d(TAG, "Locale changed from " + mKnownLocales + " to " + currentLocales
285                     + " for user " + mUserId);
286         }
287 
288         mKnownLocales = currentLocales;
289 
290         forAllPackages(pkg -> {
291             pkg.resetRateLimiting();
292             pkg.resolveResourceStrings();
293         });
294 
295         mService.scheduleSaveUser(mUserId);
296     }
297 
rescanPackageIfNeeded(@onNull String packageName, boolean forceRescan)298     public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) {
299         final boolean isNewApp = !mPackages.containsKey(packageName);
300         if (ShortcutService.DEBUG_REBOOT) {
301             Slog.d(TAG, "rescanPackageIfNeeded " + getUserId() + "@" + packageName
302                     + ", forceRescan=" + forceRescan + " , isNewApp=" + isNewApp);
303         }
304 
305         final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
306 
307         if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) {
308             if (isNewApp) {
309                 mPackages.remove(packageName);
310             }
311         }
312     }
313 
attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, @UserIdInt int packageUserId)314     public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
315             @UserIdInt int packageUserId) {
316         forPackageItem(packageName, packageUserId, spi -> {
317             spi.attemptToRestoreIfNeededAndSave();
318         });
319     }
320 
saveToXml(TypedXmlSerializer out, boolean forBackup)321     public void saveToXml(TypedXmlSerializer out, boolean forBackup)
322             throws IOException, XmlPullParserException {
323         out.startTag(null, TAG_ROOT);
324 
325         if (!forBackup) {
326             // Don't have to back them up.
327             ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
328             ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
329                     mLastAppScanTime);
330             ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT,
331                     mLastAppScanOsFingerprint);
332             ShortcutService.writeAttr(out, ATTR_RESTORE_SOURCE_FINGERPRINT,
333                     mRestoreFromOsFingerprint);
334         } else {
335             ShortcutService.writeAttr(out, ATTR_RESTORE_SOURCE_FINGERPRINT,
336                     mService.injectBuildFingerprint());
337         }
338 
339         if (!forBackup) {
340             // Since we are not handling package deletion yet, or any single package changes, just
341             // clean the directory and rewrite all the ShortcutPackageItems.
342             final File root = mService.injectUserDataPath(mUserId);
343             FileUtils.deleteContents(new File(root, DIRECTORY_PACKAGES));
344             FileUtils.deleteContents(new File(root, DIRECTORY_LUANCHERS));
345         }
346         // Can't use forEachPackageItem due to the checked exceptions.
347         {
348             final int size = mLaunchers.size();
349             for (int i = 0; i < size; i++) {
350                 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
351             }
352         }
353         {
354             final int size = mPackages.size();
355             for (int i = 0; i < size; i++) {
356                 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
357             }
358         }
359 
360         out.endTag(null, TAG_ROOT);
361     }
362 
saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi, boolean forBackup)363     private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi,
364             boolean forBackup) throws IOException, XmlPullParserException {
365         if (forBackup) {
366             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
367                 return; // Don't save cross-user information.
368             }
369             spi.waitForBitmapSaves();
370             spi.saveToXml(out, forBackup);
371         } else {
372             spi.scheduleSave();
373         }
374     }
375 
loadFromXml(ShortcutService s, TypedXmlPullParser parser, int userId, boolean fromBackup)376     public static ShortcutUser loadFromXml(ShortcutService s, TypedXmlPullParser parser, int userId,
377             boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException {
378         final ShortcutUser ret = new ShortcutUser(s, userId);
379         boolean readShortcutItems = false;
380         try {
381             ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
382                     ATTR_KNOWN_LOCALES);
383 
384             // If lastAppScanTime is in the future, that means the clock went backwards.
385             // Just scan all apps again.
386             final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
387                     ATTR_LAST_APP_SCAN_TIME);
388             final long currentTime = s.injectCurrentTimeMillis();
389             ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
390             ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
391                     ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
392             ret.mRestoreFromOsFingerprint = ShortcutService.parseStringAttribute(parser,
393                     ATTR_RESTORE_SOURCE_FINGERPRINT);
394             final int outerDepth = parser.getDepth();
395             int type;
396             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
397                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
398                 if (type != XmlPullParser.START_TAG) {
399                     continue;
400                 }
401                 final int depth = parser.getDepth();
402                 final String tag = parser.getName();
403 
404                 if (depth == outerDepth + 1) {
405                     switch (tag) {
406                         case ShortcutPackage.TAG_ROOT: {
407                             final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
408                                     s, ret, parser, fromBackup);
409 
410                             // Don't use addShortcut(), we don't need to save the icon.
411                             ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
412                             readShortcutItems = true;
413                             continue;
414                         }
415 
416                         case ShortcutLauncher.TAG_ROOT: {
417                             ret.addLauncher(
418                                     ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
419                             readShortcutItems = true;
420                             continue;
421                         }
422                     }
423                 }
424                 ShortcutService.warnForInvalidTag(depth, tag);
425             }
426         } catch (RuntimeException e) {
427             throw new ShortcutService.InvalidFileFormatException(
428                     "Unable to parse file", e);
429         }
430 
431         if (readShortcutItems) {
432             // If the shortcuts info was read from the main Xml, skip reading from individual files.
433             // Data will get stored in the new format during the next call to saveToXml().
434             // TODO: ret.forAllPackageItems((ShortcutPackageItem item) -> item.markDirty());
435             s.scheduleSaveUser(userId);
436         } else {
437             final File root = s.injectUserDataPath(userId);
438 
439             forMainFilesIn(new File(root, DIRECTORY_PACKAGES), (File f) -> {
440                 final ShortcutPackage sp = ShortcutPackage.loadFromFile(s, ret, f, fromBackup);
441                 if (sp != null) {
442                     ret.mPackages.put(sp.getPackageName(), sp);
443                 }
444             });
445 
446             forMainFilesIn(new File(root, DIRECTORY_LUANCHERS), (File f) -> {
447                 final ShortcutLauncher sl =
448                         ShortcutLauncher.loadFromFile(f, ret, userId, fromBackup);
449                 if (sl != null) {
450                     ret.addLauncher(sl);
451                 }
452             });
453         }
454 
455         return ret;
456     }
457 
forMainFilesIn(File path, Consumer<File> callback)458     private static void forMainFilesIn(File path, Consumer<File> callback) {
459         if (!path.exists()) {
460             return;
461         }
462         File[] list = path.listFiles();
463         for (File f : list) {
464             if (!f.getName().endsWith(".reservecopy") && !f.getName().endsWith(".backup")) {
465                 callback.accept(f);
466             }
467         }
468     }
469 
setCachedLauncher(String launcher)470     public void setCachedLauncher(String launcher) {
471         mCachedLauncher = launcher;
472     }
473 
getCachedLauncher()474     public String 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             sp.removeAllShortcutsAsync();
528             addPackage(sp);
529             restoredPackages[0]++;
530             restoredShortcuts[0] += sp.getShortcutCount();
531         });
532         // Empty the launchers and packages in restored to avoid accidentally using them.
533         restored.mLaunchers.clear();
534         restored.mPackages.clear();
535 
536         mRestoreFromOsFingerprint = restored.mRestoreFromOsFingerprint;
537 
538         Slog.i(TAG, "Restored: L=" + restoredLaunchers[0]
539                 + " P=" + restoredPackages[0]
540                 + " S=" + restoredShortcuts[0]);
541     }
542 
dump(@onNull PrintWriter pw, @NonNull String prefix, DumpFilter filter)543     public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
544         if (filter.shouldDumpDetails()) {
545             pw.print(prefix);
546             pw.print("User: ");
547             pw.print(mUserId);
548             pw.print("  Known locales: ");
549             pw.print(mKnownLocales);
550             pw.print("  Last app scan: [");
551             pw.print(mLastAppScanTime);
552             pw.print("] ");
553             pw.println(ShortcutService.formatTime(mLastAppScanTime));
554 
555             prefix += prefix + "  ";
556 
557             pw.print(prefix);
558             pw.print("Last app scan FP: ");
559             pw.println(mLastAppScanOsFingerprint);
560 
561             pw.print(prefix);
562             pw.print("Restore from FP: ");
563             pw.print(mRestoreFromOsFingerprint);
564             pw.println();
565 
566             pw.print(prefix);
567             pw.print("Cached launcher: ");
568             pw.print(mCachedLauncher);
569             pw.println();
570         }
571 
572         for (int i = 0; i < mLaunchers.size(); i++) {
573             ShortcutLauncher launcher = mLaunchers.valueAt(i);
574             if (filter.isPackageMatch(launcher.getPackageName())) {
575                 launcher.dump(pw, prefix, filter);
576             }
577         }
578 
579         for (int i = 0; i < mPackages.size(); i++) {
580             ShortcutPackage pkg = mPackages.valueAt(i);
581             if (filter.isPackageMatch(pkg.getPackageName())) {
582                 pkg.dump(pw, prefix, filter);
583             }
584         }
585 
586         if (filter.shouldDumpDetails()) {
587             pw.println();
588             pw.print(prefix);
589             pw.println("Bitmap directories: ");
590             dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
591         }
592     }
593 
dumpDirectorySize(@onNull PrintWriter pw, @NonNull String prefix, File path)594     private void dumpDirectorySize(@NonNull PrintWriter pw,
595             @NonNull String prefix, File path) {
596         int numFiles = 0;
597         long size = 0;
598         final File[] children = path.listFiles();
599         if (children != null) {
600             for (File child : path.listFiles()) {
601                 if (child.isFile()) {
602                     numFiles++;
603                     size += child.length();
604                 } else if (child.isDirectory()) {
605                     dumpDirectorySize(pw, prefix + "  ", child);
606                 }
607             }
608         }
609         pw.print(prefix);
610         pw.print("Path: ");
611         pw.print(path.getName());
612         pw.print("/ has ");
613         pw.print(numFiles);
614         pw.print(" files, size=");
615         pw.print(size);
616         pw.print(" (");
617         pw.print(Formatter.formatFileSize(mService.mContext, size));
618         pw.println(")");
619     }
620 
dumpCheckin(boolean clear)621     public JSONObject dumpCheckin(boolean clear) throws JSONException {
622         final JSONObject result = new JSONObject();
623 
624         result.put(KEY_USER_ID, mUserId);
625 
626         {
627             final JSONArray launchers = new JSONArray();
628             for (int i = 0; i < mLaunchers.size(); i++) {
629                 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear));
630             }
631             result.put(KEY_LAUNCHERS, launchers);
632         }
633 
634         {
635             final JSONArray packages = new JSONArray();
636             for (int i = 0; i < mPackages.size(); i++) {
637                 packages.put(mPackages.valueAt(i).dumpCheckin(clear));
638             }
639             result.put(KEY_PACKAGES, packages);
640         }
641 
642         return result;
643     }
644 
logSharingShortcutStats(MetricsLogger logger)645     void logSharingShortcutStats(MetricsLogger logger) {
646         int packageWithShareTargetsCount = 0;
647         int totalSharingShortcutCount = 0;
648         for (int i = 0; i < mPackages.size(); i++) {
649             if (mPackages.valueAt(i).hasShareTargets()) {
650                 packageWithShareTargetsCount++;
651                 totalSharingShortcutCount += mPackages.valueAt(i).getSharingShortcutCount();
652             }
653         }
654 
655         final LogMaker logMaker = new LogMaker(MetricsEvent.ACTION_SHORTCUTS_CHANGED);
656         logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_USER_ID)
657                 .setSubtype(mUserId));
658         logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_PACKAGE_COUNT)
659                 .setSubtype(packageWithShareTargetsCount));
660         logger.write(logMaker.setType(MetricsEvent.SHORTCUTS_CHANGED_SHORTCUT_COUNT)
661                 .setSubtype(totalSharingShortcutCount));
662     }
663 
664     @NonNull
getAppSearch( @onNull final AppSearchManager.SearchContext searchContext)665     AndroidFuture<AppSearchSession> getAppSearch(
666             @NonNull final AppSearchManager.SearchContext searchContext) {
667         final AndroidFuture<AppSearchSession> future = new AndroidFuture<>();
668         synchronized (mLock) {
669             mInFlightSessions.removeIf(CompletableFuture::isDone);
670             mInFlightSessions.add(future);
671         }
672         if (mAppSearchManager == null) {
673             future.completeExceptionally(new RuntimeException("app search manager is null"));
674             return future;
675         }
676         if (!mService.mUserManagerInternal.isUserUnlockingOrUnlocked(getUserId())) {
677             // In rare cases the user might be stopped immediate after it started, in these cases
678             // any on-going session will need to be abandoned.
679             future.completeExceptionally(new RuntimeException("User " + getUserId() + " is "));
680             return future;
681         }
682         final long callingIdentity = Binder.clearCallingIdentity();
683         try {
684             mAppSearchManager.createSearchSession(searchContext, mExecutor, result -> {
685                 if (!result.isSuccess()) {
686                     future.completeExceptionally(
687                             new RuntimeException(result.getErrorMessage()));
688                     return;
689                 }
690                 future.complete(result.getResultValue());
691             });
692         } finally {
693             Binder.restoreCallingIdentity(callingIdentity);
694         }
695         return future;
696     }
697 
cancelAllInFlightTasks()698     void cancelAllInFlightTasks() {
699         synchronized (mLock) {
700             for (AndroidFuture<AppSearchSession> session : mInFlightSessions) {
701                 session.cancel(true);
702             }
703             mInFlightSessions.clear();
704         }
705     }
706 }
707