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