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