ShortcutUser.java revision 3366328245621fa6e8fd764a4d3c2f073774d096
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.Slog;
27import android.util.SparseArray;
28
29import com.android.internal.annotations.GuardedBy;
30import com.android.internal.annotations.VisibleForTesting;
31import com.android.internal.util.Preconditions;
32
33import libcore.util.Objects;
34
35import org.json.JSONArray;
36import org.json.JSONException;
37import org.json.JSONObject;
38import org.xmlpull.v1.XmlPullParser;
39import org.xmlpull.v1.XmlPullParserException;
40import org.xmlpull.v1.XmlSerializer;
41
42import java.io.File;
43import java.io.IOException;
44import java.io.PrintWriter;
45import java.util.function.Consumer;
46
47/**
48 * User information used by {@link ShortcutService}.
49 *
50 * All methods should be guarded by {@code #mService.mLock}.
51 */
52class ShortcutUser {
53    private static final String TAG = ShortcutService.TAG;
54
55    static final String TAG_ROOT = "user";
56    private static final String TAG_LAUNCHER = "launcher";
57
58    private static final String ATTR_VALUE = "value";
59    private static final String ATTR_KNOWN_LOCALES = "locales";
60
61    // Suffix "2" was added to force rescan all packages after the next OTA.
62    private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time2";
63    private static final String ATTR_LAST_APP_SCAN_OS_FINGERPRINT = "last-app-scan-fp";
64    private static final String KEY_USER_ID = "userId";
65    private static final String KEY_LAUNCHERS = "launchers";
66    private static final String KEY_PACKAGES = "packages";
67
68    static final class PackageWithUser {
69        final int userId;
70        final String packageName;
71
72        private PackageWithUser(int userId, String packageName) {
73            this.userId = userId;
74            this.packageName = Preconditions.checkNotNull(packageName);
75        }
76
77        public static PackageWithUser of(int userId, String packageName) {
78            return new PackageWithUser(userId, packageName);
79        }
80
81        public static PackageWithUser of(ShortcutPackageItem spi) {
82            return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
83        }
84
85        @Override
86        public int hashCode() {
87            return packageName.hashCode() ^ userId;
88        }
89
90        @Override
91        public boolean equals(Object obj) {
92            if (!(obj instanceof PackageWithUser)) {
93                return false;
94            }
95            final PackageWithUser that = (PackageWithUser) obj;
96
97            return userId == that.userId && packageName.equals(that.packageName);
98        }
99
100        @Override
101        public String toString() {
102            return String.format("[Package: %d, %s]", userId, packageName);
103        }
104    }
105
106    final ShortcutService mService;
107
108    @UserIdInt
109    private final int mUserId;
110
111    private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
112
113    private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
114
115    /**
116     * Last known launcher.  It's used when the default launcher isn't set in PM -- i.e.
117     * when getHomeActivitiesAsUser() return null.  We need it so that in this situation the
118     * previously default launcher can still access shortcuts.
119     */
120    private ComponentName mLastKnownLauncher;
121
122    /** In-memory-cached default launcher. */
123    private ComponentName mCachedLauncher;
124
125    private String mKnownLocales;
126
127    private long mLastAppScanTime;
128
129    private String mLastAppScanOsFingerprint;
130
131    public ShortcutUser(ShortcutService service, int userId) {
132        mService = service;
133        mUserId = userId;
134    }
135
136    public int getUserId() {
137        return mUserId;
138    }
139
140    public long getLastAppScanTime() {
141        return mLastAppScanTime;
142    }
143
144    public void setLastAppScanTime(long lastAppScanTime) {
145        mLastAppScanTime = lastAppScanTime;
146    }
147
148    public String getLastAppScanOsFingerprint() {
149        return mLastAppScanOsFingerprint;
150    }
151
152    public void setLastAppScanOsFingerprint(String lastAppScanOsFingerprint) {
153        mLastAppScanOsFingerprint = lastAppScanOsFingerprint;
154    }
155
156    // We don't expose this directly to non-test code because only ShortcutUser should add to/
157    // remove from it.
158    @VisibleForTesting
159    ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
160        return mPackages;
161    }
162
163    public boolean hasPackage(@NonNull String packageName) {
164        return mPackages.containsKey(packageName);
165    }
166
167    public ShortcutPackage removePackage(@NonNull String packageName) {
168        final ShortcutPackage removed = mPackages.remove(packageName);
169
170        mService.cleanupBitmapsForPackage(mUserId, packageName);
171
172        return removed;
173    }
174
175    // We don't expose this directly to non-test code because only ShortcutUser should add to/
176    // remove from it.
177    @VisibleForTesting
178    ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
179        return mLaunchers;
180    }
181
182    public void addLauncher(ShortcutLauncher launcher) {
183        mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
184                launcher.getPackageName()), launcher);
185    }
186
187    @Nullable
188    public ShortcutLauncher removeLauncher(
189            @UserIdInt int packageUserId, @NonNull String packageName) {
190        return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
191    }
192
193    @Nullable
194    public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
195        final ShortcutPackage ret = mPackages.get(packageName);
196        if (ret != null) {
197            ret.attemptToRestoreIfNeededAndSave();
198        }
199        return ret;
200    }
201
202    @NonNull
203    public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
204        ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
205        if (ret == null) {
206            ret = new ShortcutPackage(this, mUserId, packageName);
207            mPackages.put(packageName, ret);
208        }
209        return ret;
210    }
211
212    @NonNull
213    public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
214            @UserIdInt int launcherUserId) {
215        final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
216        ShortcutLauncher ret = mLaunchers.get(key);
217        if (ret == null) {
218            ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
219            mLaunchers.put(key, ret);
220        } else {
221            ret.attemptToRestoreIfNeededAndSave();
222        }
223        return ret;
224    }
225
226    public void forAllPackages(Consumer<? super ShortcutPackage> callback) {
227        final int size = mPackages.size();
228        for (int i = 0; i < size; i++) {
229            callback.accept(mPackages.valueAt(i));
230        }
231    }
232
233    public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) {
234        final int size = mLaunchers.size();
235        for (int i = 0; i < size; i++) {
236            callback.accept(mLaunchers.valueAt(i));
237        }
238    }
239
240    public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
241        forAllLaunchers(callback);
242        forAllPackages(callback);
243    }
244
245    public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
246            Consumer<ShortcutPackageItem> callback) {
247        forAllPackageItems(spi -> {
248            if ((spi.getPackageUserId() == packageUserId)
249                    && spi.getPackageName().equals(packageName)) {
250                callback.accept(spi);
251            }
252        });
253    }
254
255    /**
256     * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the
257     * information on the package is up-to-date.
258     *
259     * We use broadcasts to handle locale changes and package changes, but because broadcasts
260     * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event
261     * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast.
262     *
263     * So we call this method at all entry points from publishers to make sure we update all
264     * relevant information.
265     *
266     * Similar inconsistencies can happen when the launcher fetches shortcut information, but
267     * that's a less of an issue because for the launcher we report shortcut changes with
268     * callbacks.
269     */
270    public void onCalledByPublisher(@NonNull String packageName) {
271        detectLocaleChange();
272        rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
273    }
274
275    private String getKnownLocales() {
276        if (TextUtils.isEmpty(mKnownLocales)) {
277            mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId);
278            mService.scheduleSaveUser(mUserId);
279        }
280        return mKnownLocales;
281    }
282
283    /**
284     * Check to see if the system locale has changed, and if so, reset throttling
285     * and update resource strings.
286     */
287    public void detectLocaleChange() {
288        final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId);
289        if (getKnownLocales().equals(currentLocales)) {
290            return;
291        }
292        if (ShortcutService.DEBUG) {
293            Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales
294                    + " for user " + mUserId);
295        }
296        mKnownLocales = currentLocales;
297
298        forAllPackages(pkg -> {
299            pkg.resetRateLimiting();
300            pkg.resolveResourceStrings();
301        });
302
303        mService.scheduleSaveUser(mUserId);
304    }
305
306    public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) {
307        final boolean isNewApp = !mPackages.containsKey(packageName);
308
309        final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
310
311        if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) {
312            if (isNewApp) {
313                mPackages.remove(packageName);
314            }
315        }
316    }
317
318    public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
319            @UserIdInt int packageUserId) {
320        forPackageItem(packageName, packageUserId, spi -> {
321            spi.attemptToRestoreIfNeededAndSave();
322        });
323    }
324
325    public void saveToXml(XmlSerializer out, boolean forBackup)
326            throws IOException, XmlPullParserException {
327        out.startTag(null, TAG_ROOT);
328
329        ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
330        ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
331                mLastAppScanTime);
332        ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT,
333                mLastAppScanOsFingerprint);
334
335        ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
336
337        // Can't use forEachPackageItem due to the checked exceptions.
338        {
339            final int size = mLaunchers.size();
340            for (int i = 0; i < size; i++) {
341                saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
342            }
343        }
344        {
345            final int size = mPackages.size();
346            for (int i = 0; i < size; i++) {
347                saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
348            }
349        }
350
351        out.endTag(null, TAG_ROOT);
352    }
353
354    private void saveShortcutPackageItem(XmlSerializer out,
355            ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
356        if (forBackup) {
357            if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
358                return; // Don't save.
359            }
360            if (spi.getPackageUserId() != spi.getOwnerUserId()) {
361                return; // Don't save cross-user information.
362            }
363        }
364        spi.saveToXml(out, forBackup);
365    }
366
367    public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
368            boolean fromBackup) throws IOException, XmlPullParserException {
369        final ShortcutUser ret = new ShortcutUser(s, userId);
370
371        ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
372                ATTR_KNOWN_LOCALES);
373
374        // If lastAppScanTime is in the future, that means the clock went backwards.
375        // Just scan all apps again.
376        final long lastAppScanTime = ShortcutService.parseLongAttribute(parser,
377                ATTR_LAST_APP_SCAN_TIME);
378        final long currentTime = s.injectCurrentTimeMillis();
379        ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0;
380        ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser,
381                ATTR_LAST_APP_SCAN_OS_FINGERPRINT);
382        final int outerDepth = parser.getDepth();
383        int type;
384        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
385                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
386            if (type != XmlPullParser.START_TAG) {
387                continue;
388            }
389            final int depth = parser.getDepth();
390            final String tag = parser.getName();
391
392            if (depth == outerDepth + 1) {
393                switch (tag) {
394                    case TAG_LAUNCHER: {
395                        ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
396                                parser, ATTR_VALUE);
397                        continue;
398                    }
399                    case ShortcutPackage.TAG_ROOT: {
400                        final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
401                                s, ret, parser, fromBackup);
402
403                        // Don't use addShortcut(), we don't need to save the icon.
404                        ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
405                        continue;
406                    }
407
408                    case ShortcutLauncher.TAG_ROOT: {
409                        ret.addLauncher(
410                                ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
411                        continue;
412                    }
413                }
414            }
415            ShortcutService.warnForInvalidTag(depth, tag);
416        }
417        return ret;
418    }
419
420    public ComponentName getLastKnownLauncher() {
421        return mLastKnownLauncher;
422    }
423
424    public void setLauncher(ComponentName launcherComponent) {
425        setLauncher(launcherComponent, /* allowPurgeLastKnown */ false);
426    }
427
428    /** Clears the launcher information without clearing the last known one */
429    public void clearLauncher() {
430        setLauncher(null);
431    }
432
433    /**
434     * Clears the launcher information *with(* clearing the last known one; we do this witl
435     * "cmd shortcut clear-default-launcher".
436     */
437    public void forceClearLauncher() {
438        setLauncher(null, /* allowPurgeLastKnown */ true);
439    }
440
441    private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) {
442        mCachedLauncher = launcherComponent; // Always update the in-memory cache.
443
444        if (Objects.equal(mLastKnownLauncher, launcherComponent)) {
445            return;
446        }
447        if (!allowPurgeLastKnown && launcherComponent == null) {
448            return;
449        }
450        mLastKnownLauncher = launcherComponent;
451        mService.scheduleSaveUser(mUserId);
452    }
453
454    public ComponentName getCachedLauncher() {
455        return mCachedLauncher;
456    }
457
458    public void resetThrottling() {
459        for (int i = mPackages.size() - 1; i >= 0; i--) {
460            mPackages.valueAt(i).resetThrottling();
461        }
462    }
463
464    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
465        pw.print(prefix);
466        pw.print("User: ");
467        pw.print(mUserId);
468        pw.print("  Known locales: ");
469        pw.print(mKnownLocales);
470        pw.print("  Last app scan: [");
471        pw.print(mLastAppScanTime);
472        pw.print("] ");
473        pw.print(ShortcutService.formatTime(mLastAppScanTime));
474        pw.print("  Last app scan FP: ");
475        pw.print(mLastAppScanOsFingerprint);
476        pw.println();
477
478        prefix += prefix + "  ";
479
480        pw.print(prefix);
481        pw.print("Cached launcher: ");
482        pw.print(mCachedLauncher);
483        pw.println();
484
485        pw.print(prefix);
486        pw.print("Last known launcher: ");
487        pw.print(mLastKnownLauncher);
488        pw.println();
489
490        for (int i = 0; i < mLaunchers.size(); i++) {
491            mLaunchers.valueAt(i).dump(pw, prefix);
492        }
493
494        for (int i = 0; i < mPackages.size(); i++) {
495            mPackages.valueAt(i).dump(pw, prefix);
496        }
497
498        pw.println();
499        pw.print(prefix);
500        pw.println("Bitmap directories: ");
501        dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
502    }
503
504    private void dumpDirectorySize(@NonNull PrintWriter pw,
505            @NonNull String prefix, File path) {
506        int numFiles = 0;
507        long size = 0;
508        final File[] children = path.listFiles();
509        if (children != null) {
510            for (File child : path.listFiles()) {
511                if (child.isFile()) {
512                    numFiles++;
513                    size += child.length();
514                } else if (child.isDirectory()) {
515                    dumpDirectorySize(pw, prefix + "  ", child);
516                }
517            }
518        }
519        pw.print(prefix);
520        pw.print("Path: ");
521        pw.print(path.getName());
522        pw.print("/ has ");
523        pw.print(numFiles);
524        pw.print(" files, size=");
525        pw.print(size);
526        pw.print(" (");
527        pw.print(Formatter.formatFileSize(mService.mContext, size));
528        pw.println(")");
529    }
530
531    public JSONObject dumpCheckin(boolean clear) throws JSONException {
532        final JSONObject result = new JSONObject();
533
534        result.put(KEY_USER_ID, mUserId);
535
536        {
537            final JSONArray launchers = new JSONArray();
538            for (int i = 0; i < mLaunchers.size(); i++) {
539                launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear));
540            }
541            result.put(KEY_LAUNCHERS, launchers);
542        }
543
544        {
545            final JSONArray packages = new JSONArray();
546            for (int i = 0; i < mPackages.size(); i++) {
547                packages.put(mPackages.valueAt(i).dumpCheckin(clear));
548            }
549            result.put(KEY_PACKAGES, packages);
550        }
551
552        return result;
553    }
554}
555