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