ShortcutUser.java revision c160fa4cc4f0ae85b3dd4169a0cd1165c183b0cf
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 */
16package com.android.server.pm;
17
18import android.annotation.NonNull;
19import android.annotation.Nullable;
20import android.annotation.UserIdInt;
21import android.content.ComponentName;
22import android.content.pm.ShortcutManager;
23import android.text.TextUtils;
24import android.text.format.Formatter;
25import android.util.ArrayMap;
26import android.util.ArraySet;
27import android.util.Log;
28import android.util.Slog;
29
30import com.android.internal.annotations.VisibleForTesting;
31import com.android.internal.util.Preconditions;
32import com.android.server.pm.ShortcutService.DumpFilter;
33import com.android.server.pm.ShortcutService.InvalidFileFormatException;
34
35import libcore.util.Objects;
36
37import org.json.JSONArray;
38import org.json.JSONException;
39import org.json.JSONObject;
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlSerializer;
43
44import java.io.File;
45import java.io.IOException;
46import java.io.PrintWriter;
47import 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 */
54class 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
74        private PackageWithUser(int userId, String packageName) {
75            this.userId = userId;
76            this.packageName = Preconditions.checkNotNull(packageName);
77        }
78
79        public static PackageWithUser of(int userId, String packageName) {
80            return new PackageWithUser(userId, packageName);
81        }
82
83        public static PackageWithUser of(ShortcutPackageItem spi) {
84            return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
85        }
86
87        @Override
88        public int hashCode() {
89            return packageName.hashCode() ^ userId;
90        }
91
92        @Override
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
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    /**
128     * Keep track of additional packages that other parts of the system have said are
129     * allowed to access shortcuts.  The key is the part of the system it came from,
130     * the value is the package name that has access.  We don't persist these because
131     * at boot all relevant system services will push this data back to us they do their
132     * normal evaluation of the state of the world.
133     */
134    private final ArrayMap<String, String> mHostPackages = new ArrayMap<>();
135
136    /**
137     * Set of package name values from above.
138     */
139    private final ArraySet<String> mHostPackageSet = new ArraySet<>();
140
141    private String mKnownLocales;
142
143    private long mLastAppScanTime;
144
145    private String mLastAppScanOsFingerprint;
146
147    public ShortcutUser(ShortcutService service, int userId) {
148        mService = service;
149        mUserId = userId;
150    }
151
152    public int getUserId() {
153        return mUserId;
154    }
155
156    public long getLastAppScanTime() {
157        return mLastAppScanTime;
158    }
159
160    public void setLastAppScanTime(long lastAppScanTime) {
161        mLastAppScanTime = lastAppScanTime;
162    }
163
164    public String getLastAppScanOsFingerprint() {
165        return mLastAppScanOsFingerprint;
166    }
167
168    public void setLastAppScanOsFingerprint(String lastAppScanOsFingerprint) {
169        mLastAppScanOsFingerprint = lastAppScanOsFingerprint;
170    }
171
172    // We don't expose this directly to non-test code because only ShortcutUser should add to/
173    // remove from it.
174    @VisibleForTesting
175    ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
176        return mPackages;
177    }
178
179    public boolean hasPackage(@NonNull String packageName) {
180        return mPackages.containsKey(packageName);
181    }
182
183    private void addPackage(@NonNull ShortcutPackage p) {
184        p.replaceUser(this);
185        mPackages.put(p.getPackageName(), p);
186    }
187
188    public ShortcutPackage removePackage(@NonNull String packageName) {
189        final ShortcutPackage removed = mPackages.remove(packageName);
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
199    ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
200        return mLaunchers;
201    }
202
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
210    public ShortcutLauncher removeLauncher(
211            @UserIdInt int packageUserId, @NonNull String packageName) {
212        return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
213    }
214
215    @Nullable
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
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
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
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
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
262    public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
263        forAllLaunchers(callback);
264        forAllPackages(callback);
265    }
266
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     */
292    public void onCalledByPublisher(@NonNull String packageName) {
293        detectLocaleChange();
294        rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
295    }
296
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     */
309    public void detectLocaleChange() {
310        final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId);
311        if (getKnownLocales().equals(currentLocales)) {
312            return;
313        }
314        if (ShortcutService.DEBUG) {
315            Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales
316                    + " for user " + mUserId);
317        }
318        mKnownLocales = currentLocales;
319
320        forAllPackages(pkg -> {
321            pkg.resetRateLimiting();
322            pkg.resolveResourceStrings();
323        });
324
325        mService.scheduleSaveUser(mUserId);
326    }
327
328    public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) {
329        final boolean isNewApp = !mPackages.containsKey(packageName);
330
331        final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
332
333        if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) {
334            if (isNewApp) {
335                mPackages.remove(packageName);
336            }
337        }
338    }
339
340    public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
341            @UserIdInt int packageUserId) {
342        forPackageItem(packageName, packageUserId, spi -> {
343            spi.attemptToRestoreIfNeededAndSave();
344        });
345    }
346
347    public void saveToXml(XmlSerializer out, boolean forBackup)
348            throws IOException, XmlPullParserException {
349        out.startTag(null, TAG_ROOT);
350
351        if (!forBackup) {
352            // Don't have to back them up.
353            ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
354            ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
355                    mLastAppScanTime);
356            ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT,
357                    mLastAppScanOsFingerprint);
358
359            ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
360        }
361
362        // Can't use forEachPackageItem due to the checked exceptions.
363        {
364            final int size = mLaunchers.size();
365            for (int i = 0; i < size; i++) {
366                saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
367            }
368        }
369        {
370            final int size = mPackages.size();
371            for (int i = 0; i < size; i++) {
372                saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
373            }
374        }
375
376        out.endTag(null, TAG_ROOT);
377    }
378
379    private void saveShortcutPackageItem(XmlSerializer out,
380            ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
381        if (forBackup) {
382            if (spi.getPackageUserId() != spi.getOwnerUserId()) {
383                return; // Don't save cross-user information.
384            }
385        }
386        spi.saveToXml(out, forBackup);
387    }
388
389    public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
390            boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException {
391        final ShortcutUser ret = new ShortcutUser(s, userId);
392
393        try {
394            ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
395                    ATTR_KNOWN_LOCALES);
396
397            // If lastAppScanTime is in the future, that means the clock went backwards.
398            // Just scan all apps again.
399            final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
400                    ATTR_LAST_APP_SCAN_TIME);
401            final long currentTime = s.injectCurrentTimeMillis();
402            ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
403            ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
404                    ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
405            final int outerDepth = parser.getDepth();
406            int type;
407            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
408                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
409                if (type != XmlPullParser.START_TAG) {
410                    continue;
411                }
412                final int depth = parser.getDepth();
413                final String tag = parser.getName();
414
415                if (depth == outerDepth + 1) {
416                    switch (tag) {
417                        case TAG_LAUNCHER: {
418                            ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
419                                    parser, ATTR_VALUE);
420                            continue;
421                        }
422                        case ShortcutPackage.TAG_ROOT: {
423                            final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
424                                    s, ret, parser, fromBackup);
425
426                            // Don't use addShortcut(), we don't need to save the icon.
427                            ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
428                            continue;
429                        }
430
431                        case ShortcutLauncher.TAG_ROOT: {
432                            ret.addLauncher(
433                                    ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
434                            continue;
435                        }
436                    }
437                }
438                ShortcutService.warnForInvalidTag(depth, tag);
439            }
440        } catch (RuntimeException e) {
441            throw new ShortcutService.InvalidFileFormatException(
442                    "Unable to parse file", e);
443        }
444        return ret;
445    }
446
447    public ComponentName getLastKnownLauncher() {
448        return mLastKnownLauncher;
449    }
450
451    public void setLauncher(ComponentName launcherComponent) {
452        setLauncher(launcherComponent, /* allowPurgeLastKnown */ false);
453    }
454
455    /** Clears the launcher information without clearing the last known one */
456    public void clearLauncher() {
457        setLauncher(null);
458    }
459
460    /**
461     * Clears the launcher information *with(* clearing the last known one; we do this witl
462     * "cmd shortcut clear-default-launcher".
463     */
464    public void forceClearLauncher() {
465        setLauncher(null, /* allowPurgeLastKnown */ true);
466    }
467
468    private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) {
469        mCachedLauncher = launcherComponent; // Always update the in-memory cache.
470
471        if (Objects.equal(mLastKnownLauncher, launcherComponent)) {
472            return;
473        }
474        if (!allowPurgeLastKnown && launcherComponent == null) {
475            return;
476        }
477        mLastKnownLauncher = launcherComponent;
478        mService.scheduleSaveUser(mUserId);
479    }
480
481    public ComponentName getCachedLauncher() {
482        return mCachedLauncher;
483    }
484
485    public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName) {
486        if (packageName != null) {
487            mHostPackages.put(type, packageName);
488        } else {
489            mHostPackages.remove(type);
490        }
491
492        mHostPackageSet.clear();
493        for (int i = 0; i < mHostPackages.size(); i++) {
494            mHostPackageSet.add(mHostPackages.valueAt(i));
495        }
496    }
497
498    public boolean hasHostPackage(@NonNull String packageName) {
499        return mHostPackageSet.contains(packageName);
500    }
501
502    public void resetThrottling() {
503        for (int i = mPackages.size() - 1; i >= 0; i--) {
504            mPackages.valueAt(i).resetThrottling();
505        }
506    }
507
508    public void mergeRestoredFile(ShortcutUser restored) {
509        final ShortcutService s = mService;
510        // Note, a restore happens only at the end of setup wizard.  At this point, no apps are
511        // installed from Play Store yet, but it's still possible that system apps have already
512        // published dynamic shortcuts, since some apps do so on BOOT_COMPLETED.
513        // When such a system app has allowbackup=true, then we go ahead and replace all existing
514        // shortcuts with the restored shortcuts.  (Then we'll re-publish manifest shortcuts later
515        // in the call site.)
516        // When such a system app has allowbackup=false, then we'll keep the shortcuts that have
517        // already been published.  So we selectively add restored ShortcutPackages here.
518        //
519        // The same logic applies to launchers, but since launchers shouldn't pin shortcuts
520        // without users interaction it's really not a big deal, so we just clear existing
521        // ShortcutLauncher instances in mLaunchers and add all the restored ones here.
522
523        int[] restoredLaunchers = new int[1];
524        int[] restoredPackages = new int[1];
525        int[] restoredShortcuts = new int[1];
526
527        mLaunchers.clear();
528        restored.forAllLaunchers(sl -> {
529            // If the app is already installed and allowbackup = false, then ignore the restored
530            // data.
531            if (s.isPackageInstalled(sl.getPackageName(), getUserId())
532                    && !s.shouldBackupApp(sl.getPackageName(), getUserId())) {
533                return;
534            }
535            addLauncher(sl);
536            restoredLaunchers[0]++;
537        });
538        restored.forAllPackages(sp -> {
539            // If the app is already installed and allowbackup = false, then ignore the restored
540            // data.
541            if (s.isPackageInstalled(sp.getPackageName(), getUserId())
542                    && !s.shouldBackupApp(sp.getPackageName(), getUserId())) {
543                return;
544            }
545
546            final ShortcutPackage previous = getPackageShortcutsIfExists(sp.getPackageName());
547            if (previous != null && previous.hasNonManifestShortcuts()) {
548                Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored."
549                        + " Existing non-manifeset shortcuts will be overwritten.");
550            }
551            addPackage(sp);
552            restoredPackages[0]++;
553            restoredShortcuts[0] += sp.getShortcutCount();
554        });
555        // Empty the launchers and packages in restored to avoid accidentally using them.
556        restored.mLaunchers.clear();
557        restored.mPackages.clear();
558
559        Slog.i(TAG, "Restored: L=" + restoredLaunchers[0]
560                + " P=" + restoredPackages[0]
561                + " S=" + restoredShortcuts[0]);
562    }
563
564    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
565        if (filter.shouldDumpDetails()) {
566            pw.print(prefix);
567            pw.print("User: ");
568            pw.print(mUserId);
569            pw.print("  Known locales: ");
570            pw.print(mKnownLocales);
571            pw.print("  Last app scan: [");
572            pw.print(mLastAppScanTime);
573            pw.print("] ");
574            pw.print(ShortcutService.formatTime(mLastAppScanTime));
575            pw.print("  Last app scan FP: ");
576            pw.print(mLastAppScanOsFingerprint);
577            pw.println();
578
579            prefix += prefix + "  ";
580
581            pw.print(prefix);
582            pw.print("Cached launcher: ");
583            pw.print(mCachedLauncher);
584            pw.println();
585
586            pw.print(prefix);
587            pw.print("Last known launcher: ");
588            pw.print(mLastKnownLauncher);
589            pw.println();
590
591            if (mHostPackages.size() > 0) {
592                pw.print(prefix);
593                pw.println("Host packages:");
594                for (int i = 0; i < mHostPackages.size(); i++) {
595                    pw.print(prefix);
596                    pw.print("  ");
597                    pw.print(mHostPackages.keyAt(i));
598                    pw.print(": ");
599                    pw.println(mHostPackages.valueAt(i));
600                }
601            }
602        }
603
604        for (int i = 0; i < mLaunchers.size(); i++) {
605            ShortcutLauncher launcher = mLaunchers.valueAt(i);
606            if (filter.isPackageMatch(launcher.getPackageName())) {
607                launcher.dump(pw, prefix, filter);
608            }
609        }
610
611        for (int i = 0; i < mPackages.size(); i++) {
612            ShortcutPackage pkg = mPackages.valueAt(i);
613            if (filter.isPackageMatch(pkg.getPackageName())) {
614                pkg.dump(pw, prefix, filter);
615            }
616        }
617
618        if (filter.shouldDumpDetails()) {
619            pw.println();
620            pw.print(prefix);
621            pw.println("Bitmap directories: ");
622            dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
623        }
624    }
625
626    private void dumpDirectorySize(@NonNull PrintWriter pw,
627            @NonNull String prefix, File path) {
628        int numFiles = 0;
629        long size = 0;
630        final File[] children = path.listFiles();
631        if (children != null) {
632            for (File child : path.listFiles()) {
633                if (child.isFile()) {
634                    numFiles++;
635                    size += child.length();
636                } else if (child.isDirectory()) {
637                    dumpDirectorySize(pw, prefix + "  ", child);
638                }
639            }
640        }
641        pw.print(prefix);
642        pw.print("Path: ");
643        pw.print(path.getName());
644        pw.print("/ has ");
645        pw.print(numFiles);
646        pw.print(" files, size=");
647        pw.print(size);
648        pw.print(" (");
649        pw.print(Formatter.formatFileSize(mService.mContext, size));
650        pw.println(")");
651    }
652
653    public JSONObject dumpCheckin(boolean clear) throws JSONException {
654        final JSONObject result = new JSONObject();
655
656        result.put(KEY_USER_ID, mUserId);
657
658        {
659            final JSONArray launchers = new JSONArray();
660            for (int i = 0; i < mLaunchers.size(); i++) {
661                launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear));
662            }
663            result.put(KEY_LAUNCHERS, launchers);
664        }
665
666        {
667            final JSONArray packages = new JSONArray();
668            for (int i = 0; i < mPackages.size(); i++) {
669                packages.put(mPackages.valueAt(i).dumpCheckin(clear));
670            }
671            result.put(KEY_PACKAGES, packages);
672        }
673
674        return result;
675    }
676}
677