ShortcutService.java revision cdc78f7137b8036dd96c92ff15fc04ee8fc49c5c
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.app.ActivityManager;
22import android.content.ComponentName;
23import android.content.ContentProvider;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.IShortcutService;
27import android.content.pm.LauncherApps;
28import android.content.pm.LauncherApps.ShortcutQuery;
29import android.content.pm.PackageManager;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.content.pm.PackageManagerInternal;
32import android.content.pm.ParceledListSlice;
33import android.content.pm.ResolveInfo;
34import android.content.pm.ShortcutInfo;
35import android.content.pm.ShortcutServiceInternal;
36import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
37import android.graphics.Bitmap;
38import android.graphics.Bitmap.CompressFormat;
39import android.graphics.BitmapFactory;
40import android.graphics.Canvas;
41import android.graphics.RectF;
42import android.graphics.drawable.Icon;
43import android.net.Uri;
44import android.os.Binder;
45import android.os.Environment;
46import android.os.Handler;
47import android.os.Looper;
48import android.os.ParcelFileDescriptor;
49import android.os.PersistableBundle;
50import android.os.Process;
51import android.os.RemoteException;
52import android.os.ResultReceiver;
53import android.os.SELinux;
54import android.os.ShellCommand;
55import android.os.UserHandle;
56import android.os.UserManager;
57import android.text.TextUtils;
58import android.text.format.Formatter;
59import android.text.format.Time;
60import android.util.ArrayMap;
61import android.util.ArraySet;
62import android.util.AtomicFile;
63import android.util.KeyValueListParser;
64import android.util.Slog;
65import android.util.SparseArray;
66import android.util.TypedValue;
67import android.util.Xml;
68
69import com.android.internal.annotations.GuardedBy;
70import com.android.internal.annotations.VisibleForTesting;
71import com.android.internal.content.PackageMonitor;
72import com.android.internal.os.BackgroundThread;
73import com.android.internal.util.FastXmlSerializer;
74import com.android.internal.util.Preconditions;
75import com.android.server.LocalServices;
76import com.android.server.SystemService;
77
78import libcore.io.IoUtils;
79import libcore.util.Objects;
80
81import org.xmlpull.v1.XmlPullParser;
82import org.xmlpull.v1.XmlPullParserException;
83import org.xmlpull.v1.XmlSerializer;
84
85import java.io.File;
86import java.io.FileDescriptor;
87import java.io.FileInputStream;
88import java.io.FileNotFoundException;
89import java.io.FileOutputStream;
90import java.io.IOException;
91import java.io.InputStream;
92import java.io.PrintWriter;
93import java.net.URISyntaxException;
94import java.nio.charset.StandardCharsets;
95import java.util.ArrayList;
96import java.util.List;
97import java.util.function.Predicate;
98
99/**
100 * TODO:
101 *
102 * - Default launcher check does take a few ms.  Worth caching.
103 *
104 * - Allow non-default launcher to start pinned shortcuts. (but not dynamic.)
105 *
106 * - Extract the user/package/launcher classes to their own files.  Maybe rename so they all have
107 *   the same "Shortcut" prefix.
108 *
109 * - Listen to PACKAGE_*, remove orphan info, update timestamp for icon res
110 *   -> Need to scan all packages when a user starts too.
111 *   -> Clear data -> remove all dynamic?  but not the pinned?
112 *
113 * - Scan and remove orphan bitmaps (just in case).
114 *
115 * - Backup & restore
116 *
117 * - Detect when already registered instances are passed to APIs again, which might break
118 *   internal bitmap handling.
119 */
120public class ShortcutService extends IShortcutService.Stub {
121    static final String TAG = "ShortcutService";
122
123    static final boolean DEBUG = false; // STOPSHIP if true
124    static final boolean DEBUG_LOAD = false; // STOPSHIP if true
125
126    @VisibleForTesting
127    static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
128
129    @VisibleForTesting
130    static final int DEFAULT_MAX_DAILY_UPDATES = 10;
131
132    @VisibleForTesting
133    static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
134
135    @VisibleForTesting
136    static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
137
138    @VisibleForTesting
139    static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
140
141    @VisibleForTesting
142    static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
143
144    @VisibleForTesting
145    static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
146
147    @VisibleForTesting
148    static final int DEFAULT_SAVE_DELAY_MS = 3000;
149
150    @VisibleForTesting
151    static final String FILENAME_BASE_STATE = "shortcut_service.xml";
152
153    @VisibleForTesting
154    static final String DIRECTORY_PER_USER = "shortcut_service";
155
156    @VisibleForTesting
157    static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
158
159    static final String DIRECTORY_BITMAPS = "bitmaps";
160
161    private static final String TAG_ROOT = "root";
162    private static final String TAG_LAST_RESET_TIME = "last_reset_time";
163
164    private static final String ATTR_VALUE = "value";
165
166    @VisibleForTesting
167    interface ConfigConstants {
168        /**
169         * Key name for the save delay, in milliseconds. (int)
170         */
171        String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
172
173        /**
174         * Key name for the throttling reset interval, in seconds. (long)
175         */
176        String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
177
178        /**
179         * Key name for the max number of modifying API calls per app for every interval. (int)
180         */
181        String KEY_MAX_DAILY_UPDATES = "max_daily_updates";
182
183        /**
184         * Key name for the max icon dimensions in DP, for non-low-memory devices.
185         */
186        String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
187
188        /**
189         * Key name for the max icon dimensions in DP, for low-memory devices.
190         */
191        String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
192
193        /**
194         * Key name for the max dynamic shortcuts per app. (int)
195         */
196        String KEY_MAX_SHORTCUTS = "max_shortcuts";
197
198        /**
199         * Key name for icon compression quality, 0-100.
200         */
201        String KEY_ICON_QUALITY = "icon_quality";
202
203        /**
204         * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
205         */
206        String KEY_ICON_FORMAT = "icon_format";
207    }
208
209    final Context mContext;
210
211    private final Object mLock = new Object();
212
213    private final Handler mHandler;
214
215    @GuardedBy("mLock")
216    private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
217
218    @GuardedBy("mLock")
219    private long mRawLastResetTime;
220
221    /**
222     * User ID -> UserShortcuts
223     */
224    @GuardedBy("mLock")
225    private final SparseArray<UserShortcuts> mUsers = new SparseArray<>();
226
227    /**
228     * Max number of dynamic shortcuts that each application can have at a time.
229     */
230    private int mMaxDynamicShortcuts;
231
232    /**
233     * Max number of updating API calls that each application can make a day.
234     */
235    int mMaxDailyUpdates;
236
237    /**
238     * Actual throttling-reset interval.  By default it's a day.
239     */
240    private long mResetInterval;
241
242    /**
243     * Icon max width/height in pixels.
244     */
245    private int mMaxIconDimension;
246
247    private CompressFormat mIconPersistFormat;
248    private int mIconPersistQuality;
249
250    private int mSaveDelayMillis;
251
252    private final PackageManagerInternal mPackageManagerInternal;
253    private final UserManager mUserManager;
254
255    @GuardedBy("mLock")
256    private List<Integer> mDirtyUserIds = new ArrayList<>();
257
258    public ShortcutService(Context context) {
259        this(context, BackgroundThread.get().getLooper());
260    }
261
262    @VisibleForTesting
263    ShortcutService(Context context, Looper looper) {
264        mContext = Preconditions.checkNotNull(context);
265        LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
266        mHandler = new Handler(looper);
267        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
268        mUserManager = context.getSystemService(UserManager.class);
269
270        mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
271    }
272
273    /**
274     * System service lifecycle.
275     */
276    public static final class Lifecycle extends SystemService {
277        final ShortcutService mService;
278
279        public Lifecycle(Context context) {
280            super(context);
281            mService = new ShortcutService(context);
282        }
283
284        @Override
285        public void onStart() {
286            publishBinderService(Context.SHORTCUT_SERVICE, mService);
287        }
288
289        @Override
290        public void onBootPhase(int phase) {
291            mService.onBootPhase(phase);
292        }
293
294        @Override
295        public void onCleanupUser(int userHandle) {
296            mService.handleCleanupUser(userHandle);
297        }
298
299        @Override
300        public void onUnlockUser(int userId) {
301            mService.handleUnlockUser(userId);
302        }
303    }
304
305    /** lifecycle event */
306    void onBootPhase(int phase) {
307        if (DEBUG) {
308            Slog.d(TAG, "onBootPhase: " + phase);
309        }
310        switch (phase) {
311            case SystemService.PHASE_LOCK_SETTINGS_READY:
312                initialize();
313                break;
314        }
315    }
316
317    /** lifecycle event */
318    void handleUnlockUser(int userId) {
319        synchronized (mLock) {
320            // Preload
321            getUserShortcutsLocked(userId);
322        }
323    }
324
325    /** lifecycle event */
326    void handleCleanupUser(int userId) {
327        synchronized (mLock) {
328            unloadUserLocked(userId);
329        }
330    }
331
332    private void unloadUserLocked(int userId) {
333        if (DEBUG) {
334            Slog.d(TAG, "unloadUserLocked: user=" + userId);
335        }
336        // Save all dirty information.
337        saveDirtyInfo();
338
339        // Unload
340        mUsers.delete(userId);
341    }
342
343    /** Return the base state file name */
344    private AtomicFile getBaseStateFile() {
345        final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
346        path.mkdirs();
347        return new AtomicFile(path);
348    }
349
350    /**
351     * Init the instance. (load the state file, etc)
352     */
353    private void initialize() {
354        synchronized (mLock) {
355            loadConfigurationLocked();
356            loadBaseStateLocked();
357        }
358    }
359
360    /**
361     * Load the configuration from Settings.
362     */
363    private void loadConfigurationLocked() {
364        updateConfigurationLocked(injectShortcutManagerConstants());
365    }
366
367    /**
368     * Load the configuration from Settings.
369     */
370    @VisibleForTesting
371    boolean updateConfigurationLocked(String config) {
372        boolean result = true;
373
374        final KeyValueListParser parser = new KeyValueListParser(',');
375        try {
376            parser.setString(config);
377        } catch (IllegalArgumentException e) {
378            // Failed to parse the settings string, log this and move on
379            // with defaults.
380            Slog.e(TAG, "Bad shortcut manager settings", e);
381            result = false;
382        }
383
384        mSaveDelayMillis = (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
385                DEFAULT_SAVE_DELAY_MS);
386
387        mResetInterval = parser.getLong(
388                ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
389                * 1000L;
390
391        mMaxDailyUpdates = (int) parser.getLong(
392                ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES);
393
394        mMaxDynamicShortcuts = (int) parser.getLong(
395                ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP);
396
397        final int iconDimensionDp = injectIsLowRamDevice()
398                ? (int) parser.getLong(
399                    ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
400                    DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
401                : (int) parser.getLong(
402                    ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
403                    DEFAULT_MAX_ICON_DIMENSION_DP);
404
405        mMaxIconDimension = injectDipToPixel(iconDimensionDp);
406
407        mIconPersistFormat = CompressFormat.valueOf(
408                parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
409
410        mIconPersistQuality = (int) parser.getLong(
411                ConfigConstants.KEY_ICON_QUALITY,
412                DEFAULT_ICON_PERSIST_QUALITY);
413
414        return result;
415    }
416
417    @VisibleForTesting
418    String injectShortcutManagerConstants() {
419        return android.provider.Settings.Global.getString(
420                mContext.getContentResolver(),
421                android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
422    }
423
424    @VisibleForTesting
425    int injectDipToPixel(int dip) {
426        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
427                mContext.getResources().getDisplayMetrics());
428    }
429
430    // === Persisting ===
431
432    @Nullable
433    static String parseStringAttribute(XmlPullParser parser, String attribute) {
434        return parser.getAttributeValue(null, attribute);
435    }
436
437    static int parseIntAttribute(XmlPullParser parser, String attribute) {
438        return (int) parseLongAttribute(parser, attribute);
439    }
440
441    static long parseLongAttribute(XmlPullParser parser, String attribute) {
442        final String value = parseStringAttribute(parser, attribute);
443        if (TextUtils.isEmpty(value)) {
444            return 0;
445        }
446        try {
447            return Long.parseLong(value);
448        } catch (NumberFormatException e) {
449            Slog.e(TAG, "Error parsing long " + value);
450            return 0;
451        }
452    }
453
454    @Nullable
455    static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
456        final String value = parseStringAttribute(parser, attribute);
457        if (TextUtils.isEmpty(value)) {
458            return null;
459        }
460        return ComponentName.unflattenFromString(value);
461    }
462
463    @Nullable
464    static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
465        final String value = parseStringAttribute(parser, attribute);
466        if (TextUtils.isEmpty(value)) {
467            return null;
468        }
469        try {
470            return Intent.parseUri(value, /* flags =*/ 0);
471        } catch (URISyntaxException e) {
472            Slog.e(TAG, "Error parsing intent", e);
473            return null;
474        }
475    }
476
477    static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
478        if (TextUtils.isEmpty(value)) return;
479
480        out.startTag(null, tag);
481        out.attribute(null, ATTR_VALUE, value);
482        out.endTag(null, tag);
483    }
484
485    static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
486        writeTagValue(out, tag, Long.toString(value));
487    }
488
489    static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
490        if (name == null) return;
491        writeTagValue(out, tag, name.flattenToString());
492    }
493
494    static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
495            throws IOException, XmlPullParserException {
496        if (bundle == null) return;
497
498        out.startTag(null, tag);
499        bundle.saveToXml(out);
500        out.endTag(null, tag);
501    }
502
503    static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
504        if (TextUtils.isEmpty(value)) return;
505
506        out.attribute(null, name, value);
507    }
508
509    static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
510        writeAttr(out, name, String.valueOf(value));
511    }
512
513    static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
514        if (comp == null) return;
515        writeAttr(out, name, comp.flattenToString());
516    }
517
518    static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
519        if (intent == null) return;
520
521        writeAttr(out, name, intent.toUri(/* flags =*/ 0));
522    }
523
524    @VisibleForTesting
525    void saveBaseStateLocked() {
526        final AtomicFile file = getBaseStateFile();
527        if (DEBUG) {
528            Slog.d(TAG, "Saving to " + file.getBaseFile());
529        }
530
531        FileOutputStream outs = null;
532        try {
533            outs = file.startWrite();
534
535            // Write to XML
536            XmlSerializer out = new FastXmlSerializer();
537            out.setOutput(outs, StandardCharsets.UTF_8.name());
538            out.startDocument(null, true);
539            out.startTag(null, TAG_ROOT);
540
541            // Body.
542            writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
543
544            // Epilogue.
545            out.endTag(null, TAG_ROOT);
546            out.endDocument();
547
548            // Close.
549            file.finishWrite(outs);
550        } catch (IOException e) {
551            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
552            file.failWrite(outs);
553        }
554    }
555
556    private void loadBaseStateLocked() {
557        mRawLastResetTime = 0;
558
559        final AtomicFile file = getBaseStateFile();
560        if (DEBUG) {
561            Slog.d(TAG, "Loading from " + file.getBaseFile());
562        }
563        try (FileInputStream in = file.openRead()) {
564            XmlPullParser parser = Xml.newPullParser();
565            parser.setInput(in, StandardCharsets.UTF_8.name());
566
567            int type;
568            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
569                if (type != XmlPullParser.START_TAG) {
570                    continue;
571                }
572                final int depth = parser.getDepth();
573                // Check the root tag
574                final String tag = parser.getName();
575                if (depth == 1) {
576                    if (!TAG_ROOT.equals(tag)) {
577                        Slog.e(TAG, "Invalid root tag: " + tag);
578                        return;
579                    }
580                    continue;
581                }
582                // Assume depth == 2
583                switch (tag) {
584                    case TAG_LAST_RESET_TIME:
585                        mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
586                        break;
587                    default:
588                        Slog.e(TAG, "Invalid tag: " + tag);
589                        break;
590                }
591            }
592        } catch (FileNotFoundException e) {
593            // Use the default
594        } catch (IOException|XmlPullParserException e) {
595            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
596
597            mRawLastResetTime = 0;
598        }
599        // Adjust the last reset time.
600        getLastResetTimeLocked();
601    }
602
603    private void saveUserLocked(@UserIdInt int userId) {
604        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
605        if (DEBUG) {
606            Slog.d(TAG, "Saving to " + path);
607        }
608        path.mkdirs();
609        final AtomicFile file = new AtomicFile(path);
610        FileOutputStream outs = null;
611        try {
612            outs = file.startWrite();
613
614            // Write to XML
615            XmlSerializer out = new FastXmlSerializer();
616            out.setOutput(outs, StandardCharsets.UTF_8.name());
617            out.startDocument(null, true);
618
619            getUserShortcutsLocked(userId).saveToXml(out);
620
621            out.endDocument();
622
623            // Close.
624            file.finishWrite(outs);
625        } catch (IOException|XmlPullParserException e) {
626            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
627            file.failWrite(outs);
628        }
629    }
630
631    static IOException throwForInvalidTag(int depth, String tag) throws IOException {
632        throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
633    }
634
635    @Nullable
636    private UserShortcuts loadUserLocked(@UserIdInt int userId) {
637        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
638        if (DEBUG) {
639            Slog.d(TAG, "Loading from " + path);
640        }
641        final AtomicFile file = new AtomicFile(path);
642
643        final FileInputStream in;
644        try {
645            in = file.openRead();
646        } catch (FileNotFoundException e) {
647            if (DEBUG) {
648                Slog.d(TAG, "Not found " + path);
649            }
650            return null;
651        }
652        UserShortcuts ret = null;
653        try {
654            XmlPullParser parser = Xml.newPullParser();
655            parser.setInput(in, StandardCharsets.UTF_8.name());
656
657            int type;
658            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
659                if (type != XmlPullParser.START_TAG) {
660                    continue;
661                }
662                final int depth = parser.getDepth();
663
664                final String tag = parser.getName();
665                if (DEBUG_LOAD) {
666                    Slog.d(TAG, String.format("depth=%d type=%d name=%s",
667                            depth, type, tag));
668                }
669                if ((depth == 1) && UserShortcuts.TAG_ROOT.equals(tag)) {
670                    ret = UserShortcuts.loadFromXml(parser, userId);
671                    continue;
672                }
673                throwForInvalidTag(depth, tag);
674            }
675            return ret;
676        } catch (IOException|XmlPullParserException e) {
677            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
678            return null;
679        } finally {
680            IoUtils.closeQuietly(in);
681        }
682    }
683
684    private void scheduleSaveBaseState() {
685        scheduleSave(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
686    }
687
688    void scheduleSaveUser(@UserIdInt int userId) {
689        scheduleSave(userId);
690    }
691
692    // In order to re-schedule, we need to reuse the same instance, so keep it in final.
693    private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo;
694
695    private void scheduleSave(@UserIdInt int userId) {
696        if (DEBUG) {
697            Slog.d(TAG, "Scheduling to save for " + userId);
698        }
699        synchronized (mLock) {
700            if (!mDirtyUserIds.contains(userId)) {
701                mDirtyUserIds.add(userId);
702            }
703        }
704        // If already scheduled, remove that and re-schedule in N seconds.
705        mHandler.removeCallbacks(mSaveDirtyInfoRunner);
706        mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
707    }
708
709    @VisibleForTesting
710    void saveDirtyInfo() {
711        if (DEBUG) {
712            Slog.d(TAG, "saveDirtyInfo");
713        }
714        synchronized (mLock) {
715            for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
716                final int userId = mDirtyUserIds.get(i);
717                if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
718                    saveBaseStateLocked();
719                } else {
720                    saveUserLocked(userId);
721                }
722            }
723            mDirtyUserIds.clear();
724        }
725    }
726
727    /** Return the last reset time. */
728    long getLastResetTimeLocked() {
729        updateTimesLocked();
730        return mRawLastResetTime;
731    }
732
733    /** Return the next reset time. */
734    long getNextResetTimeLocked() {
735        updateTimesLocked();
736        return mRawLastResetTime + mResetInterval;
737    }
738
739    static boolean isClockValid(long time) {
740        return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
741    }
742
743    /**
744     * Update the last reset time.
745     */
746    private void updateTimesLocked() {
747
748        final long now = injectCurrentTimeMillis();
749
750        final long prevLastResetTime = mRawLastResetTime;
751
752        if (mRawLastResetTime == 0) { // first launch.
753            // TODO Randomize??
754            mRawLastResetTime = now;
755        } else if (now < mRawLastResetTime) {
756            // Clock rewound.
757            if (isClockValid(now)) {
758                Slog.w(TAG, "Clock rewound");
759                // TODO Randomize??
760                mRawLastResetTime = now;
761            }
762        } else {
763            if ((mRawLastResetTime + mResetInterval) <= now) {
764                final long offset = mRawLastResetTime % mResetInterval;
765                mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
766            }
767        }
768        if (prevLastResetTime != mRawLastResetTime) {
769            scheduleSaveBaseState();
770        }
771    }
772
773    @GuardedBy("mLock")
774    @NonNull
775    boolean isUserLoadedLocked(@UserIdInt int userId) {
776        return mUsers.get(userId) != null;
777    }
778
779    /** Return the per-user state. */
780    @GuardedBy("mLock")
781    @NonNull
782    UserShortcuts getUserShortcutsLocked(@UserIdInt int userId) {
783        UserShortcuts userPackages = mUsers.get(userId);
784        if (userPackages == null) {
785            userPackages = loadUserLocked(userId);
786            if (userPackages == null) {
787                userPackages = new UserShortcuts(userId);
788            }
789            mUsers.put(userId, userPackages);
790        }
791        return userPackages;
792    }
793
794    /** Return the per-user per-package state. */
795    @GuardedBy("mLock")
796    @NonNull
797    PackageShortcuts getPackageShortcutsLocked(
798            @NonNull String packageName, @UserIdInt int userId) {
799        return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
800    }
801
802    @GuardedBy("mLock")
803    @NonNull
804    LauncherShortcuts getLauncherShortcuts(
805            @NonNull String packageName, @UserIdInt int userId) {
806        return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName);
807    }
808
809    // === Caller validation ===
810
811    void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
812        if (shortcut.getBitmapPath() != null) {
813            if (DEBUG) {
814                Slog.d(TAG, "Removing " + shortcut.getBitmapPath());
815            }
816            new File(shortcut.getBitmapPath()).delete();
817
818            shortcut.setBitmapPath(null);
819            shortcut.setIconResourceId(0);
820            shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
821        }
822    }
823
824    @VisibleForTesting
825    static class FileOutputStreamWithPath extends FileOutputStream {
826        private final File mFile;
827
828        public FileOutputStreamWithPath(File file) throws FileNotFoundException {
829            super(file);
830            mFile = file;
831        }
832
833        public File getFile() {
834            return mFile;
835        }
836    }
837
838    /**
839     * Build the cached bitmap filename for a shortcut icon.
840     *
841     * The filename will be based on the ID, except certain characters will be escaped.
842     */
843    @VisibleForTesting
844    FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
845            throws IOException {
846        final File packagePath = new File(getUserBitmapFilePath(userId),
847                shortcut.getPackageName());
848        if (!packagePath.isDirectory()) {
849            packagePath.mkdirs();
850            if (!packagePath.isDirectory()) {
851                throw new IOException("Unable to create directory " + packagePath);
852            }
853            SELinux.restorecon(packagePath);
854        }
855
856        final String baseName = String.valueOf(injectCurrentTimeMillis());
857        for (int suffix = 0;; suffix++) {
858            final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
859            final File file = new File(packagePath, filename);
860            if (!file.exists()) {
861                if (DEBUG) {
862                    Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
863                }
864                return new FileOutputStreamWithPath(file);
865            }
866        }
867    }
868
869    void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) {
870        if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
871            return;
872        }
873
874        final long token = injectClearCallingIdentity();
875        try {
876            // Clear icon info on the shortcut.
877            shortcut.setIconResourceId(0);
878            shortcut.setBitmapPath(null);
879
880            final Icon icon = shortcut.getIcon();
881            if (icon == null) {
882                return; // has no icon
883            }
884
885            Bitmap bitmap = null;
886            try {
887                switch (icon.getType()) {
888                    case Icon.TYPE_RESOURCE: {
889                        injectValidateIconResPackage(shortcut, icon);
890
891                        shortcut.setIconResourceId(icon.getResId());
892                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
893                        return;
894                    }
895                    case Icon.TYPE_BITMAP: {
896                        bitmap = icon.getBitmap();
897                        break;
898                    }
899                    case Icon.TYPE_URI: {
900                        final Uri uri = ContentProvider.maybeAddUserId(icon.getUri(), userId);
901
902                        try (InputStream is = mContext.getContentResolver().openInputStream(uri)) {
903
904                            bitmap = BitmapFactory.decodeStream(is);
905
906                        } catch (IOException e) {
907                            Slog.e(TAG, "Unable to load icon from " + uri);
908                            return;
909                        }
910                        break;
911                    }
912                    default:
913                        // This shouldn't happen because we've already validated the icon, but
914                        // just in case.
915                        throw ShortcutInfo.getInvalidIconException();
916                }
917                if (bitmap == null) {
918                    Slog.e(TAG, "Null bitmap detected");
919                    return;
920                }
921                // Shrink and write to the file.
922                File path = null;
923                try {
924                    final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut);
925                    try {
926                        path = out.getFile();
927
928                        shrinkBitmap(bitmap, mMaxIconDimension)
929                                .compress(mIconPersistFormat, mIconPersistQuality, out);
930
931                        shortcut.setBitmapPath(out.getFile().getAbsolutePath());
932                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
933                    } finally {
934                        IoUtils.closeQuietly(out);
935                    }
936                } catch (IOException|RuntimeException e) {
937                    // STOPSHIP Change wtf to e
938                    Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
939                    if (path != null && path.exists()) {
940                        path.delete();
941                    }
942                }
943            } finally {
944                if (bitmap != null) {
945                    bitmap.recycle();
946                }
947                // Once saved, we won't use the original icon information, so null it out.
948                shortcut.clearIcon();
949            }
950        } finally {
951            injectRestoreCallingIdentity(token);
952        }
953    }
954
955    // Unfortunately we can't do this check in unit tests because we fake creator package names,
956    // so override in unit tests.
957    // TODO CTS this case.
958    void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
959        if (!shortcut.getPackageName().equals(icon.getResPackage())) {
960            throw new IllegalArgumentException(
961                    "Icon resource must reside in shortcut owner package");
962        }
963    }
964
965    @VisibleForTesting
966    static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
967        // Original width/height.
968        final int ow = in.getWidth();
969        final int oh = in.getHeight();
970        if ((ow <= maxSize) && (oh <= maxSize)) {
971            if (DEBUG) {
972                Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
973            }
974            return in;
975        }
976        final int longerDimension = Math.max(ow, oh);
977
978        // New width and height.
979        final int nw = ow * maxSize / longerDimension;
980        final int nh = oh * maxSize / longerDimension;
981        if (DEBUG) {
982            Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
983                    ow, oh, nw, nh));
984        }
985
986        final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
987        final Canvas c = new Canvas(scaledBitmap);
988
989        final RectF dst = new RectF(0, 0, nw, nh);
990
991        c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
992
993        in.recycle();
994
995        return scaledBitmap;
996    }
997
998    // === Caller validation ===
999
1000    private boolean isCallerSystem() {
1001        final int callingUid = injectBinderCallingUid();
1002         return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
1003    }
1004
1005    private boolean isCallerShell() {
1006        final int callingUid = injectBinderCallingUid();
1007        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
1008    }
1009
1010    private void enforceSystemOrShell() {
1011        Preconditions.checkState(isCallerSystem() || isCallerShell(),
1012                "Caller must be system or shell");
1013    }
1014
1015    private void enforceShell() {
1016        Preconditions.checkState(isCallerShell(), "Caller must be shell");
1017    }
1018
1019    private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
1020        Preconditions.checkStringNotEmpty(packageName, "packageName");
1021
1022        if (isCallerSystem()) {
1023            return; // no check
1024        }
1025
1026        final int callingUid = injectBinderCallingUid();
1027
1028        // Otherwise, make sure the arguments are valid.
1029        if (UserHandle.getUserId(callingUid) != userId) {
1030            throw new SecurityException("Invalid user-ID");
1031        }
1032        if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) {
1033            return; // Caller is valid.
1034        }
1035        throw new SecurityException("Caller UID= doesn't own " + packageName);
1036    }
1037
1038    // Test overrides it.
1039    int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
1040        try {
1041            return mContext.getPackageManager().getPackageUidAsUser(packageName,
1042                    PackageManager.MATCH_DIRECT_BOOT_AWARE
1043                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
1044                            | PackageManager.MATCH_UNINSTALLED_PACKAGES,
1045                    userId);
1046        } catch (NameNotFoundException e) {
1047            return -1;
1048        }
1049    }
1050
1051    void postToHandler(Runnable r) {
1052        mHandler.post(r);
1053    }
1054
1055    /**
1056     * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
1057     */
1058    void enforceMaxDynamicShortcuts(int numShortcuts) {
1059        if (numShortcuts > mMaxDynamicShortcuts) {
1060            throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
1061        }
1062    }
1063
1064    /**
1065     * - Sends a notification to LauncherApps
1066     * - Write to file
1067     */
1068    private void userPackageChanged(@NonNull String packageName, @UserIdInt int userId) {
1069        notifyListeners(packageName, userId);
1070        scheduleSaveUser(userId);
1071    }
1072
1073    private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
1074        if (!mUserManager.isUserRunning(userId)) {
1075            return;
1076        }
1077        postToHandler(() -> {
1078            final ArrayList<ShortcutChangeListener> copy;
1079            synchronized (mLock) {
1080                copy = new ArrayList<>(mListeners);
1081            }
1082            // Note onShortcutChanged() needs to be called with the system service permissions.
1083            for (int i = copy.size() - 1; i >= 0; i--) {
1084                copy.get(i).onShortcutChanged(packageName, userId);
1085            }
1086        });
1087    }
1088
1089    /**
1090     * Clean up / validate an incoming shortcut.
1091     * - Make sure all mandatory fields are set.
1092     * - Make sure the intent's extras are persistable, and them to set
1093     *  {@link ShortcutInfo#mIntentPersistableExtras}.  Also clear its extras.
1094     * - Clear flags.
1095     *
1096     * TODO Detailed unit tests
1097     */
1098    private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
1099        Preconditions.checkNotNull(shortcut, "Null shortcut detected");
1100        if (shortcut.getActivityComponent() != null) {
1101            Preconditions.checkState(
1102                    shortcut.getPackageName().equals(
1103                            shortcut.getActivityComponent().getPackageName()),
1104                    "Activity package name mismatch");
1105        }
1106
1107        if (!forUpdate) {
1108            shortcut.enforceMandatoryFields();
1109        }
1110        if (shortcut.getIcon() != null) {
1111            ShortcutInfo.validateIcon(shortcut.getIcon());
1112        }
1113
1114        validateForXml(shortcut.getId());
1115        validateForXml(shortcut.getTitle());
1116        validatePersistableBundleForXml(shortcut.getIntentPersistableExtras());
1117        validatePersistableBundleForXml(shortcut.getExtras());
1118
1119        shortcut.replaceFlags(0);
1120    }
1121
1122    // KXmlSerializer is strict and doesn't allow certain characters, so we disallow those
1123    // characters.
1124
1125    private static void validatePersistableBundleForXml(PersistableBundle b) {
1126        if (b == null || b.size() == 0) {
1127            return;
1128        }
1129        for (String key : b.keySet()) {
1130            validateForXml(key);
1131            final Object value = b.get(key);
1132            if (value == null) {
1133                continue;
1134            } else if (value instanceof String) {
1135                validateForXml((String) value);
1136            } else if (value instanceof String[]) {
1137                for (String v : (String[]) value) {
1138                    validateForXml(v);
1139                }
1140            } else if (value instanceof PersistableBundle) {
1141                validatePersistableBundleForXml((PersistableBundle) value);
1142            }
1143        }
1144    }
1145
1146    private static void validateForXml(String s) {
1147        if (TextUtils.isEmpty(s)) {
1148            return;
1149        }
1150        for (int i = s.length() - 1; i >= 0; i--) {
1151            if (!isAllowedInXml(s.charAt(i))) {
1152                throw new IllegalArgumentException("Unsupported character detected in: " + s);
1153            }
1154        }
1155    }
1156
1157    private static boolean isAllowedInXml(char c) {
1158        return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
1159    }
1160
1161    // === APIs ===
1162
1163    @Override
1164    public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1165            @UserIdInt int userId) {
1166        verifyCaller(packageName, userId);
1167
1168        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1169        final int size = newShortcuts.size();
1170
1171        synchronized (mLock) {
1172            final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId);
1173
1174            // Throttling.
1175            if (!ps.tryApiCall(this)) {
1176                return false;
1177            }
1178            enforceMaxDynamicShortcuts(size);
1179
1180            // Validate the shortcuts.
1181            for (int i = 0; i < size; i++) {
1182                fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
1183            }
1184
1185            // First, remove all un-pinned; dynamic shortcuts
1186            ps.deleteAllDynamicShortcuts(this);
1187
1188            // Then, add/update all.  We need to make sure to take over "pinned" flag.
1189            for (int i = 0; i < size; i++) {
1190                final ShortcutInfo newShortcut = newShortcuts.get(i);
1191                ps.addDynamicShortcut(this, newShortcut);
1192            }
1193        }
1194        userPackageChanged(packageName, userId);
1195        return true;
1196    }
1197
1198    @Override
1199    public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1200            @UserIdInt int userId) {
1201        verifyCaller(packageName, userId);
1202
1203        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1204        final int size = newShortcuts.size();
1205
1206        synchronized (mLock) {
1207            final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId);
1208
1209            // Throttling.
1210            if (!ps.tryApiCall(this)) {
1211                return false;
1212            }
1213
1214            for (int i = 0; i < size; i++) {
1215                final ShortcutInfo source = newShortcuts.get(i);
1216                fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
1217
1218                final ShortcutInfo target = ps.findShortcutById(source.getId());
1219                if (target != null) {
1220                    final boolean replacingIcon = (source.getIcon() != null);
1221                    if (replacingIcon) {
1222                        removeIcon(userId, target);
1223                    }
1224
1225                    target.copyNonNullFieldsFrom(source);
1226
1227                    if (replacingIcon) {
1228                        saveIconAndFixUpShortcut(userId, target);
1229                    }
1230                }
1231            }
1232        }
1233        userPackageChanged(packageName, userId);
1234
1235        return true;
1236    }
1237
1238    @Override
1239    public boolean addDynamicShortcut(String packageName, ShortcutInfo newShortcut,
1240            @UserIdInt int userId) {
1241        verifyCaller(packageName, userId);
1242
1243        synchronized (mLock) {
1244            final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId);
1245
1246            // Throttling.
1247            if (!ps.tryApiCall(this)) {
1248                return false;
1249            }
1250
1251            // Validate the shortcut.
1252            fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
1253
1254            // Add it.
1255            ps.addDynamicShortcut(this, newShortcut);
1256        }
1257        userPackageChanged(packageName, userId);
1258
1259        return true;
1260    }
1261
1262    @Override
1263    public void deleteDynamicShortcut(String packageName, String shortcutId,
1264            @UserIdInt int userId) {
1265        verifyCaller(packageName, userId);
1266        Preconditions.checkStringNotEmpty(shortcutId, "shortcutId must be provided");
1267
1268        synchronized (mLock) {
1269            getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this, shortcutId);
1270        }
1271        userPackageChanged(packageName, userId);
1272    }
1273
1274    @Override
1275    public void deleteAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
1276        verifyCaller(packageName, userId);
1277
1278        synchronized (mLock) {
1279            getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
1280        }
1281        userPackageChanged(packageName, userId);
1282    }
1283
1284    @Override
1285    public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
1286            @UserIdInt int userId) {
1287        verifyCaller(packageName, userId);
1288        synchronized (mLock) {
1289            return getShortcutsWithQueryLocked(
1290                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1291                    ShortcutInfo::isDynamic);
1292        }
1293    }
1294
1295    @Override
1296    public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
1297            @UserIdInt int userId) {
1298        verifyCaller(packageName, userId);
1299        synchronized (mLock) {
1300            return getShortcutsWithQueryLocked(
1301                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1302                    ShortcutInfo::isPinned);
1303        }
1304    }
1305
1306    private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
1307            @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
1308
1309        final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1310
1311        getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags,
1312                /* callingLauncher= */ null);
1313
1314        return new ParceledListSlice<>(ret);
1315    }
1316
1317    @Override
1318    public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId)
1319            throws RemoteException {
1320        verifyCaller(packageName, userId);
1321
1322        return mMaxDynamicShortcuts;
1323    }
1324
1325    @Override
1326    public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
1327        verifyCaller(packageName, userId);
1328
1329        synchronized (mLock) {
1330            return mMaxDailyUpdates
1331                    - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
1332        }
1333    }
1334
1335    @Override
1336    public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
1337        verifyCaller(packageName, userId);
1338
1339        synchronized (mLock) {
1340            return getNextResetTimeLocked();
1341        }
1342    }
1343
1344    @Override
1345    public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
1346        synchronized (mLock) {
1347            return mMaxIconDimension;
1348        }
1349    }
1350
1351    /**
1352     * Reset all throttling, for developer options and command line.  Only system/shell can call it.
1353     */
1354    @Override
1355    public void resetThrottling() {
1356        enforceSystemOrShell();
1357
1358        resetThrottlingInner(getCallingUserId());
1359    }
1360
1361    void resetThrottlingInner(@UserIdInt int userId) {
1362        synchronized (mLock) {
1363            getUserShortcutsLocked(userId).resetThrottling();
1364        }
1365        scheduleSaveUser(userId);
1366        Slog.i(TAG, "ShortcutManager: throttling counter reset");
1367    }
1368
1369    // We override this method in unit tests to do a simpler check.
1370    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
1371        return hasShortcutHostPermissionInner(callingPackage, userId);
1372    }
1373
1374    // This method is extracted so we can directly call this method from unit tests,
1375    // even when hasShortcutPermission() is overridden.
1376    @VisibleForTesting
1377    boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
1378        synchronized (mLock) {
1379            long start = 0;
1380            if (DEBUG) {
1381                start = System.currentTimeMillis();
1382            }
1383
1384            final UserShortcuts user = getUserShortcutsLocked(userId);
1385
1386            final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
1387
1388            // Default launcher from package manager.
1389            final ComponentName defaultLauncher = injectPackageManagerInternal()
1390                    .getHomeActivitiesAsUser(allHomeCandidates, userId);
1391
1392            ComponentName detected;
1393            if (defaultLauncher != null) {
1394                detected = defaultLauncher;
1395                if (DEBUG) {
1396                    Slog.v(TAG, "Default launcher from PM: " + detected);
1397                }
1398            } else {
1399                detected = user.getLauncherComponent();
1400
1401                // TODO: Make sure it's still enabled.
1402                if (DEBUG) {
1403                    Slog.v(TAG, "Cached launcher: " + detected);
1404                }
1405            }
1406
1407            if (detected == null) {
1408                // If we reach here, that means it's the first check since the user was created,
1409                // and there's already multiple launchers and there's no default set.
1410                // Find the system one with the highest priority.
1411                // (We need to check the priority too because of FallbackHome in Settings.)
1412                // If there's no system launcher yet, then no one can access shortcuts, until
1413                // the user explicitly
1414                final int size = allHomeCandidates.size();
1415
1416                int lastPriority = Integer.MIN_VALUE;
1417                for (int i = 0; i < size; i++) {
1418                    final ResolveInfo ri = allHomeCandidates.get(i);
1419                    if (!ri.activityInfo.applicationInfo.isSystemApp()) {
1420                        continue;
1421                    }
1422                    if (DEBUG) {
1423                        Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
1424                                ri.activityInfo.getComponentName(), ri.priority));
1425                    }
1426                    if (ri.priority < lastPriority) {
1427                        continue;
1428                    }
1429                    detected = ri.activityInfo.getComponentName();
1430                    lastPriority = ri.priority;
1431                }
1432            }
1433            if (DEBUG) {
1434                long end = System.currentTimeMillis();
1435                Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start));
1436            }
1437            if (detected != null) {
1438                if (DEBUG) {
1439                    Slog.v(TAG, "Detected launcher: " + detected);
1440                }
1441                user.setLauncherComponent(this, detected);
1442                return detected.getPackageName().equals(callingPackage);
1443            } else {
1444                // Default launcher not found.
1445                return false;
1446            }
1447        }
1448    }
1449
1450    // === House keeping ===
1451
1452    @VisibleForTesting
1453    void cleanUpPackageLocked(String packageName, int userId) {
1454        final boolean wasUserLoaded = isUserLoadedLocked(userId);
1455
1456        final UserShortcuts mUser = getUserShortcutsLocked(userId);
1457        boolean doNotify = false;
1458
1459        // First, remove the package from the package list (if the package is a publisher).
1460        if (mUser.getPackages().remove(packageName) != null) {
1461            doNotify = true;
1462        }
1463        // Also remove from the launcher list (if the package is a launcher).
1464        mUser.getLaunchers().remove(packageName);
1465
1466        // Then remove pinned shortcuts from all launchers.
1467        for (int i = mUser.getLaunchers().size() - 1; i >= 0; i--) {
1468            mUser.getLaunchers().valueAt(i).cleanUpPackage(packageName);
1469        }
1470        // Now there may be orphan shortcuts because we removed pinned shortucts at the previous
1471        // step.  Remove them too.
1472        for (int i = mUser.getPackages().size() - 1; i >= 0; i--) {
1473            mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
1474        }
1475
1476        scheduleSaveUser(userId);
1477
1478        if (doNotify) {
1479            notifyListeners(packageName, userId);
1480        }
1481
1482        if (!wasUserLoaded) {
1483            // Note this will execute the scheduled save.
1484            unloadUserLocked(userId);
1485        }
1486    }
1487
1488    /**
1489     * Entry point from {@link LauncherApps}.
1490     */
1491    private class LocalService extends ShortcutServiceInternal {
1492        @Override
1493        public List<ShortcutInfo> getShortcuts(
1494                @NonNull String callingPackage, long changedSince,
1495                @Nullable String packageName, @Nullable ComponentName componentName,
1496                int queryFlags, int userId) {
1497            final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1498            final int cloneFlag =
1499                    ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0)
1500                            ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER
1501                            : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
1502
1503            synchronized (mLock) {
1504                if (packageName != null) {
1505                    getShortcutsInnerLocked(
1506                            callingPackage, packageName, changedSince,
1507                            componentName, queryFlags, userId, ret, cloneFlag);
1508                } else {
1509                    final ArrayMap<String, PackageShortcuts> packages =
1510                            getUserShortcutsLocked(userId).getPackages();
1511                    for (int i = packages.size() - 1; i >= 0; i--) {
1512                        getShortcutsInnerLocked(
1513                                callingPackage, packages.keyAt(i), changedSince,
1514                                componentName, queryFlags, userId, ret, cloneFlag);
1515                    }
1516                }
1517            }
1518            return ret;
1519        }
1520
1521        private void getShortcutsInnerLocked(@NonNull String callingPackage,
1522                @Nullable String packageName,long changedSince,
1523                @Nullable ComponentName componentName, int queryFlags,
1524                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
1525            getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
1526                    (ShortcutInfo si) -> {
1527                        if (si.getLastChangedTimestamp() < changedSince) {
1528                            return false;
1529                        }
1530                        if (componentName != null
1531                                && !componentName.equals(si.getActivityComponent())) {
1532                            return false;
1533                        }
1534                        final boolean matchDynamic =
1535                                ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
1536                                        && si.isDynamic();
1537                        final boolean matchPinned =
1538                                ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
1539                                        && si.isPinned();
1540                        return matchDynamic || matchPinned;
1541                    }, cloneFlag, callingPackage);
1542        }
1543
1544        @Override
1545        public List<ShortcutInfo> getShortcutInfo(
1546                @NonNull String callingPackage,
1547                @NonNull String packageName, @Nullable List<String> ids, int userId) {
1548            // Calling permission must be checked by LauncherAppsImpl.
1549            Preconditions.checkStringNotEmpty(packageName, "packageName");
1550
1551            final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
1552            final ArraySet<String> idSet = new ArraySet<>(ids);
1553            synchronized (mLock) {
1554                getPackageShortcutsLocked(packageName, userId).findAll(
1555                        ShortcutService.this, ret,
1556                        (ShortcutInfo si) -> idSet.contains(si.getId()),
1557                        ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER, callingPackage);
1558            }
1559            return ret;
1560        }
1561
1562        @Override
1563        public void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName,
1564                @NonNull List<String> shortcutIds, int userId) {
1565            // Calling permission must be checked by LauncherAppsImpl.
1566            Preconditions.checkStringNotEmpty(packageName, "packageName");
1567            Preconditions.checkNotNull(shortcutIds, "shortcutIds");
1568
1569            synchronized (mLock) {
1570                getLauncherShortcuts(callingPackage, userId).pinShortcuts(
1571                        ShortcutService.this, packageName, shortcutIds);
1572            }
1573            userPackageChanged(packageName, userId);
1574        }
1575
1576        @Override
1577        public Intent createShortcutIntent(@NonNull String callingPackage,
1578                @NonNull String packageName, @NonNull String shortcutId, int userId) {
1579            // Calling permission must be checked by LauncherAppsImpl.
1580            Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
1581            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
1582
1583            synchronized (mLock) {
1584                final ShortcutInfo fullShortcut =
1585                        getPackageShortcutsLocked(packageName, userId)
1586                        .findShortcutById(shortcutId);
1587                return fullShortcut == null ? null : fullShortcut.getIntent();
1588            }
1589        }
1590
1591        @Override
1592        public void addListener(@NonNull ShortcutChangeListener listener) {
1593            synchronized (mLock) {
1594                mListeners.add(Preconditions.checkNotNull(listener));
1595            }
1596        }
1597
1598        @Override
1599        public int getShortcutIconResId(@NonNull String callingPackage,
1600                @NonNull ShortcutInfo shortcut, int userId) {
1601            Preconditions.checkNotNull(shortcut, "shortcut");
1602
1603            synchronized (mLock) {
1604                final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1605                        shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
1606                return (shortcutInfo != null && shortcutInfo.hasIconResource())
1607                        ? shortcutInfo.getIconResourceId() : 0;
1608            }
1609        }
1610
1611        @Override
1612        public ParcelFileDescriptor getShortcutIconFd(@NonNull String callingPackage,
1613                @NonNull ShortcutInfo shortcutIn, int userId) {
1614            Preconditions.checkNotNull(shortcutIn, "shortcut");
1615
1616            synchronized (mLock) {
1617                final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1618                        shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
1619                if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
1620                    return null;
1621                }
1622                try {
1623                    if (shortcutInfo.getBitmapPath() == null) {
1624                        Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
1625                        return null;
1626                    }
1627                    return ParcelFileDescriptor.open(
1628                            new File(shortcutInfo.getBitmapPath()),
1629                            ParcelFileDescriptor.MODE_READ_ONLY);
1630                } catch (FileNotFoundException e) {
1631                    Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath());
1632                    return null;
1633                }
1634            }
1635        }
1636
1637        @Override
1638        public boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
1639            return ShortcutService.this.hasShortcutHostPermission(callingPackage, userId);
1640        }
1641    }
1642
1643    private PackageMonitor mPackageMonitor = new PackageMonitor() {
1644        @Override
1645        public void onPackageUpdateFinished(String packageName, int uid) {
1646            handlePackageUpdateFinished(packageName, getChangingUserId());
1647        }
1648
1649        @Override
1650        public void onPackageRemoved(String packageName, int uid) {
1651            handlePackageRemoved(packageName, getChangingUserId());
1652        }
1653
1654        @Override
1655        public void onPackageRemovedAllUsers(String packageName, int uid) {
1656            handlePackageRemovedAllUsers(packageName, getChangingUserId());
1657        }
1658    };
1659
1660    void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
1661        if (DEBUG) {
1662            Slog.d(TAG, "onPackageUpdateFinished() userId=" + userId);
1663        }
1664        // TODO Update the version.
1665    }
1666
1667    void handlePackageRemoved(String packageName, @UserIdInt int userId) {
1668        if (DEBUG) {
1669            Slog.d(TAG, "onPackageRemoved() userId=" + userId);
1670        }
1671        synchronized (mLock) {
1672            cleanUpPackageLocked(packageName, userId);
1673        }
1674    }
1675
1676    void handlePackageRemovedAllUsers(String packageName, @UserIdInt int userId) {
1677        if (DEBUG) {
1678            Slog.d(TAG, "onPackageRemovedAllUsers() userId=" + userId);
1679        }
1680        synchronized (mLock) {
1681            cleanUpPackageLocked(packageName, userId);
1682        }
1683
1684        // TODO Remove from all users, which we can't if the user is locked.
1685    }
1686
1687    // === Dump ===
1688
1689    @Override
1690    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1691        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1692                != PackageManager.PERMISSION_GRANTED) {
1693            pw.println("Permission Denial: can't dump UserManager from from pid="
1694                    + Binder.getCallingPid()
1695                    + ", uid=" + Binder.getCallingUid()
1696                    + " without permission "
1697                    + android.Manifest.permission.DUMP);
1698            return;
1699        }
1700        dumpInner(pw);
1701    }
1702
1703    @VisibleForTesting
1704    void dumpInner(PrintWriter pw) {
1705        synchronized (mLock) {
1706            final long now = injectCurrentTimeMillis();
1707            pw.print("Now: [");
1708            pw.print(now);
1709            pw.print("] ");
1710            pw.print(formatTime(now));
1711
1712            pw.print("  Raw last reset: [");
1713            pw.print(mRawLastResetTime);
1714            pw.print("] ");
1715            pw.print(formatTime(mRawLastResetTime));
1716
1717            final long last = getLastResetTimeLocked();
1718            pw.print("  Last reset: [");
1719            pw.print(last);
1720            pw.print("] ");
1721            pw.print(formatTime(last));
1722
1723            final long next = getNextResetTimeLocked();
1724            pw.print("  Next reset: [");
1725            pw.print(next);
1726            pw.print("] ");
1727            pw.print(formatTime(next));
1728            pw.println();
1729
1730            pw.print("  Max icon dim: ");
1731            pw.print(mMaxIconDimension);
1732            pw.print("  Icon format: ");
1733            pw.print(mIconPersistFormat);
1734            pw.print("  Icon quality: ");
1735            pw.print(mIconPersistQuality);
1736            pw.println();
1737
1738
1739            for (int i = 0; i < mUsers.size(); i++) {
1740                pw.println();
1741                mUsers.valueAt(i).dump(this, pw, "  ");
1742            }
1743        }
1744    }
1745
1746    static String formatTime(long time) {
1747        Time tobj = new Time();
1748        tobj.set(time);
1749        return tobj.format("%Y-%m-%d %H:%M:%S");
1750    }
1751
1752    // === Shell support ===
1753
1754    @Override
1755    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1756            String[] args, ResultReceiver resultReceiver) throws RemoteException {
1757
1758        enforceShell();
1759
1760        (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
1761    }
1762
1763    static class CommandException extends Exception {
1764        public CommandException(String message) {
1765            super(message);
1766        }
1767    }
1768
1769    /**
1770     * Handle "adb shell cmd".
1771     */
1772    private class MyShellCommand extends ShellCommand {
1773
1774        private int mUserId = UserHandle.USER_SYSTEM;
1775
1776        private void parseOptions(boolean takeUser)
1777                throws CommandException {
1778            String opt;
1779            while ((opt = getNextOption()) != null) {
1780                switch (opt) {
1781                    case "--user":
1782                        if (takeUser) {
1783                            mUserId = UserHandle.parseUserArg(getNextArgRequired());
1784                            break;
1785                        }
1786                        // fallthrough
1787                    default:
1788                        throw new CommandException("Unknown option: " + opt);
1789                }
1790            }
1791        }
1792
1793        @Override
1794        public int onCommand(String cmd) {
1795            if (cmd == null) {
1796                return handleDefaultCommands(cmd);
1797            }
1798            final PrintWriter pw = getOutPrintWriter();
1799            try {
1800                switch (cmd) {
1801                    case "reset-package-throttling":
1802                        handleResetPackageThrottling();
1803                        break;
1804                    case "reset-throttling":
1805                        handleResetThrottling();
1806                        break;
1807                    case "override-config":
1808                        handleOverrideConfig();
1809                        break;
1810                    case "reset-config":
1811                        handleResetConfig();
1812                        break;
1813                    case "clear-default-launcher":
1814                        handleClearDefaultLauncher();
1815                        break;
1816                    case "get-default-launcher":
1817                        handleGetDefaultLauncher();
1818                        break;
1819                    case "refresh-default-launcher":
1820                        handleRefreshDefaultLauncher();
1821                        break;
1822                    default:
1823                        return handleDefaultCommands(cmd);
1824                }
1825            } catch (CommandException e) {
1826                pw.println("Error: " + e.getMessage());
1827                return 1;
1828            }
1829            pw.println("Success");
1830            return 0;
1831        }
1832
1833        @Override
1834        public void onHelp() {
1835            final PrintWriter pw = getOutPrintWriter();
1836            pw.println("Usage: cmd shortcut COMMAND [options ...]");
1837            pw.println();
1838            pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
1839            pw.println("    Reset throttling for a package");
1840            pw.println();
1841            pw.println("cmd shortcut reset-throttling");
1842            pw.println("    Reset throttling for all packages and users");
1843            pw.println();
1844            pw.println("cmd shortcut override-config CONFIG");
1845            pw.println("    Override the configuration for testing (will last until reboot)");
1846            pw.println();
1847            pw.println("cmd shortcut reset-config");
1848            pw.println("    Reset the configuration set with \"update-config\"");
1849            pw.println();
1850            pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
1851            pw.println("    Clear the cached default launcher");
1852            pw.println();
1853            pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
1854            pw.println("    Show the cached default launcher");
1855            pw.println();
1856            pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
1857            pw.println("    Refresh the cached default launcher");
1858            pw.println();
1859        }
1860
1861        private int handleResetThrottling() throws CommandException {
1862            parseOptions(/* takeUser =*/ true);
1863
1864            resetThrottlingInner(mUserId);
1865            return 0;
1866        }
1867
1868        private void handleResetPackageThrottling() throws CommandException {
1869            parseOptions(/* takeUser =*/ true);
1870
1871            final String packageName = getNextArgRequired();
1872
1873            synchronized (mLock) {
1874                getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine();
1875                saveUserLocked(mUserId);
1876            }
1877        }
1878
1879        private void handleOverrideConfig() throws CommandException {
1880            final String config = getNextArgRequired();
1881
1882            synchronized (mLock) {
1883                if (!updateConfigurationLocked(config)) {
1884                    throw new CommandException("override-config failed.  See logcat for details.");
1885                }
1886            }
1887        }
1888
1889        private void handleResetConfig() {
1890            synchronized (mLock) {
1891                loadConfigurationLocked();
1892            }
1893        }
1894
1895        private void clearLauncher() {
1896            synchronized (mLock) {
1897                getUserShortcutsLocked(mUserId).setLauncherComponent(
1898                        ShortcutService.this, null);
1899            }
1900        }
1901
1902        private void showLauncher() {
1903            synchronized (mLock) {
1904                // This ensures to set the cached launcher.  Package name doesn't matter.
1905                hasShortcutHostPermissionInner("-", mUserId);
1906
1907                getOutPrintWriter().println("Launcher: "
1908                        + getUserShortcutsLocked(mUserId).getLauncherComponent());
1909            }
1910        }
1911
1912        private void handleClearDefaultLauncher() throws CommandException {
1913            parseOptions(/* takeUser =*/ true);
1914
1915            clearLauncher();
1916        }
1917
1918        private void handleGetDefaultLauncher() throws CommandException {
1919            parseOptions(/* takeUser =*/ true);
1920
1921            showLauncher();
1922        }
1923
1924        private void handleRefreshDefaultLauncher() throws CommandException {
1925            parseOptions(/* takeUser =*/ true);
1926
1927            clearLauncher();
1928            showLauncher();
1929        }
1930    }
1931
1932    // === Unit test support ===
1933
1934    // Injection point.
1935    long injectCurrentTimeMillis() {
1936        return System.currentTimeMillis();
1937    }
1938
1939    // Injection point.
1940    int injectBinderCallingUid() {
1941        return getCallingUid();
1942    }
1943
1944    final int getCallingUserId() {
1945        return UserHandle.getUserId(injectBinderCallingUid());
1946    }
1947
1948    // Injection point.
1949    long injectClearCallingIdentity() {
1950        return Binder.clearCallingIdentity();
1951    }
1952
1953    // Injection point.
1954    void injectRestoreCallingIdentity(long token) {
1955        Binder.restoreCallingIdentity(token);
1956    }
1957
1958    final void wtf(String message) {
1959        Slog.wtf(TAG, message, /* exception= */ null);
1960    }
1961
1962    void wtf(String message, Exception e) {
1963        Slog.wtf(TAG, message, e);
1964    }
1965
1966    File injectSystemDataPath() {
1967        return Environment.getDataSystemDirectory();
1968    }
1969
1970    File injectUserDataPath(@UserIdInt int userId) {
1971        return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
1972    }
1973
1974    @VisibleForTesting
1975    boolean injectIsLowRamDevice() {
1976        return ActivityManager.isLowRamDeviceStatic();
1977    }
1978
1979    PackageManagerInternal injectPackageManagerInternal() {
1980        return mPackageManagerInternal;
1981    }
1982
1983    File getUserBitmapFilePath(@UserIdInt int userId) {
1984        return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
1985    }
1986
1987    @VisibleForTesting
1988    SparseArray<UserShortcuts> getShortcutsForTest() {
1989        return mUsers;
1990    }
1991
1992    @VisibleForTesting
1993    int getMaxDynamicShortcutsForTest() {
1994        return mMaxDynamicShortcuts;
1995    }
1996
1997    @VisibleForTesting
1998    int getMaxDailyUpdatesForTest() {
1999        return mMaxDailyUpdates;
2000    }
2001
2002    @VisibleForTesting
2003    long getResetIntervalForTest() {
2004        return mResetInterval;
2005    }
2006
2007    @VisibleForTesting
2008    int getMaxIconDimensionForTest() {
2009        return mMaxIconDimension;
2010    }
2011
2012    @VisibleForTesting
2013    CompressFormat getIconPersistFormatForTest() {
2014        return mIconPersistFormat;
2015    }
2016
2017    @VisibleForTesting
2018    int getIconPersistQualityForTest() {
2019        return mIconPersistQuality;
2020    }
2021
2022    @VisibleForTesting
2023    ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
2024        synchronized (mLock) {
2025            final UserShortcuts user = mUsers.get(userId);
2026            if (user == null) return null;
2027
2028            final PackageShortcuts pkg = user.getPackages().get(packageName);
2029            if (pkg == null) return null;
2030
2031            return pkg.findShortcutById(shortcutId);
2032        }
2033    }
2034}
2035
2036/**
2037 * Per-user information.
2038 */
2039class UserShortcuts {
2040    private static final String TAG = ShortcutService.TAG;
2041
2042    static final String TAG_ROOT = "user";
2043    private static final String TAG_LAUNCHER = "launcher";
2044
2045    private static final String ATTR_VALUE = "value";
2046
2047    @UserIdInt
2048    final int mUserId;
2049
2050    private final ArrayMap<String, PackageShortcuts> mPackages = new ArrayMap<>();
2051
2052    private final ArrayMap<String, LauncherShortcuts> mLaunchers = new ArrayMap<>();
2053
2054    private ComponentName mLauncherComponent;
2055
2056    public UserShortcuts(int userId) {
2057        mUserId = userId;
2058    }
2059
2060    public ArrayMap<String, PackageShortcuts> getPackages() {
2061        return mPackages;
2062    }
2063
2064    public ArrayMap<String, LauncherShortcuts> getLaunchers() {
2065        return mLaunchers;
2066    }
2067
2068    public PackageShortcuts getPackageShortcuts(@NonNull String packageName) {
2069        PackageShortcuts ret = mPackages.get(packageName);
2070        if (ret == null) {
2071            ret = new PackageShortcuts(mUserId, packageName);
2072            mPackages.put(packageName, ret);
2073        }
2074        return ret;
2075    }
2076
2077    public LauncherShortcuts getLauncherShortcuts(@NonNull String packageName) {
2078        LauncherShortcuts ret = mLaunchers.get(packageName);
2079        if (ret == null) {
2080            ret = new LauncherShortcuts(mUserId, packageName);
2081            mLaunchers.put(packageName, ret);
2082        }
2083        return ret;
2084    }
2085
2086    public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
2087        out.startTag(null, TAG_ROOT);
2088
2089        ShortcutService.writeTagValue(out, TAG_LAUNCHER,
2090                mLauncherComponent);
2091
2092        final int lsize = mLaunchers.size();
2093        for (int i = 0; i < lsize; i++) {
2094            mLaunchers.valueAt(i).saveToXml(out);
2095        }
2096
2097        final int psize = mPackages.size();
2098        for (int i = 0; i < psize; i++) {
2099            mPackages.valueAt(i).saveToXml(out);
2100        }
2101
2102        out.endTag(null, TAG_ROOT);
2103    }
2104
2105    public static UserShortcuts loadFromXml(XmlPullParser parser, int userId)
2106            throws IOException, XmlPullParserException {
2107        final UserShortcuts ret = new UserShortcuts(userId);
2108
2109        final int outerDepth = parser.getDepth();
2110        int type;
2111        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
2112                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
2113            if (type != XmlPullParser.START_TAG) {
2114                continue;
2115            }
2116            final int depth = parser.getDepth();
2117            final String tag = parser.getName();
2118            switch (tag) {
2119                case TAG_LAUNCHER: {
2120                    ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
2121                            parser, ATTR_VALUE);
2122                    continue;
2123                }
2124                case PackageShortcuts.TAG_ROOT: {
2125                    final PackageShortcuts shortcuts = PackageShortcuts.loadFromXml(parser, userId);
2126
2127                    // Don't use addShortcut(), we don't need to save the icon.
2128                    ret.getPackages().put(shortcuts.mPackageName, shortcuts);
2129                    continue;
2130                }
2131
2132                case LauncherShortcuts.TAG_ROOT: {
2133                    final LauncherShortcuts shortcuts =
2134                            LauncherShortcuts.loadFromXml(parser, userId);
2135
2136                    ret.getLaunchers().put(shortcuts.mPackageName, shortcuts);
2137                    continue;
2138                }
2139            }
2140            throw ShortcutService.throwForInvalidTag(depth, tag);
2141        }
2142        return ret;
2143    }
2144
2145    public ComponentName getLauncherComponent() {
2146        return mLauncherComponent;
2147    }
2148
2149    public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) {
2150        if (Objects.equal(mLauncherComponent, launcherComponent)) {
2151            return;
2152        }
2153        mLauncherComponent = launcherComponent;
2154        s.scheduleSaveUser(mUserId);
2155    }
2156
2157    public void resetThrottling() {
2158        for (int i = mPackages.size() - 1; i >= 0; i--) {
2159            mPackages.valueAt(i).resetThrottling();
2160        }
2161    }
2162
2163    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
2164        pw.print(prefix);
2165        pw.print("User: ");
2166        pw.print(mUserId);
2167        pw.println();
2168
2169        pw.print(prefix);
2170        pw.print("  ");
2171        pw.print("Default launcher: ");
2172        pw.print(mLauncherComponent);
2173        pw.println();
2174
2175        for (int i = 0; i < mLaunchers.size(); i++) {
2176            mLaunchers.valueAt(i).dump(s, pw, prefix + "  ");
2177        }
2178
2179        for (int i = 0; i < mPackages.size(); i++) {
2180            mPackages.valueAt(i).dump(s, pw, prefix + "  ");
2181        }
2182    }
2183}
2184
2185class LauncherShortcuts {
2186    private static final String TAG = ShortcutService.TAG;
2187
2188    static final String TAG_ROOT = "launcher-pins";
2189
2190    private static final String TAG_PACKAGE = "package";
2191    private static final String TAG_PIN = "pin";
2192
2193    private static final String ATTR_VALUE = "value";
2194    private static final String ATTR_PACKAGE_NAME = "package-name";
2195
2196    @UserIdInt
2197    final int mUserId;
2198
2199    @NonNull
2200    final String mPackageName;
2201
2202    /**
2203     * Package name -> IDs.
2204     */
2205    final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
2206
2207    LauncherShortcuts(@UserIdInt int userId, @NonNull String packageName) {
2208        mUserId = userId;
2209        mPackageName = packageName;
2210    }
2211
2212    public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName,
2213            @NonNull List<String> ids) {
2214        final int idSize = ids.size();
2215        if (idSize == 0) {
2216            mPinnedShortcuts.remove(packageName);
2217        } else {
2218            final ArraySet<String> prevSet = mPinnedShortcuts.get(packageName);
2219
2220            // Pin shortcuts.  Make sure only pin the ones that were visible to the caller.
2221            // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
2222
2223            final PackageShortcuts packageShortcuts =
2224                    s.getPackageShortcutsLocked(packageName, mUserId);
2225            final ArraySet<String> newSet = new ArraySet<>();
2226
2227            for (int i = 0; i < idSize; i++) {
2228                final String id = ids.get(i);
2229                final ShortcutInfo si = packageShortcuts.findShortcutById(id);
2230                if (si == null) {
2231                    continue;
2232                }
2233                if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) {
2234                    newSet.add(id);
2235                }
2236            }
2237            mPinnedShortcuts.put(packageName, newSet);
2238        }
2239        s.getPackageShortcutsLocked(packageName, mUserId).refreshPinnedFlags(s);
2240    }
2241
2242    /**
2243     * Return the pinned shortcut IDs for the publisher package.
2244     */
2245    public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName) {
2246        return mPinnedShortcuts.get(packageName);
2247    }
2248
2249    boolean cleanUpPackage(String packageName) {
2250        return mPinnedShortcuts.remove(packageName) != null;
2251    }
2252
2253    /**
2254     * Persist.
2255     */
2256    public void saveToXml(XmlSerializer out) throws IOException {
2257        final int size = mPinnedShortcuts.size();
2258        if (size == 0) {
2259            return; // Nothing to write.
2260        }
2261
2262        out.startTag(null, TAG_ROOT);
2263        ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
2264                mPackageName);
2265
2266        for (int i = 0; i < size; i++) {
2267            out.startTag(null, TAG_PACKAGE);
2268            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
2269                    mPinnedShortcuts.keyAt(i));
2270
2271            final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
2272            final int idSize = ids.size();
2273            for (int j = 0; j < idSize; j++) {
2274                ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j));
2275            }
2276            out.endTag(null, TAG_PACKAGE);
2277        }
2278
2279        out.endTag(null, TAG_ROOT);
2280    }
2281
2282    /**
2283     * Load.
2284     */
2285    public static LauncherShortcuts loadFromXml(XmlPullParser parser, int userId)
2286            throws IOException, XmlPullParserException {
2287        final String launcherPackageName = ShortcutService.parseStringAttribute(parser,
2288                ATTR_PACKAGE_NAME);
2289
2290        final LauncherShortcuts ret = new LauncherShortcuts(userId, launcherPackageName);
2291
2292        ArraySet<String> ids = null;
2293        final int outerDepth = parser.getDepth();
2294        int type;
2295        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
2296                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
2297            if (type != XmlPullParser.START_TAG) {
2298                continue;
2299            }
2300            final int depth = parser.getDepth();
2301            final String tag = parser.getName();
2302            switch (tag) {
2303                case TAG_PACKAGE: {
2304                    final String packageName = ShortcutService.parseStringAttribute(parser,
2305                            ATTR_PACKAGE_NAME);
2306                    ids = new ArraySet<>();
2307                    ret.mPinnedShortcuts.put(packageName, ids);
2308                    continue;
2309                }
2310                case TAG_PIN: {
2311                    ids.add(ShortcutService.parseStringAttribute(parser,
2312                            ATTR_VALUE));
2313                    continue;
2314                }
2315            }
2316            throw ShortcutService.throwForInvalidTag(depth, tag);
2317        }
2318        return ret;
2319    }
2320
2321    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
2322        pw.println();
2323
2324        pw.print(prefix);
2325        pw.print("Launcher: ");
2326        pw.print(mPackageName);
2327        pw.println();
2328
2329        final int size = mPinnedShortcuts.size();
2330        for (int i = 0; i < size; i++) {
2331            pw.println();
2332
2333            pw.print(prefix);
2334            pw.print("  ");
2335            pw.print("Package: ");
2336            pw.println(mPinnedShortcuts.keyAt(i));
2337
2338            final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
2339            final int idSize = ids.size();
2340
2341            for (int j = 0; j < idSize; j++) {
2342                pw.print(prefix);
2343                pw.print("    ");
2344                pw.print(ids.valueAt(j));
2345                pw.println();
2346            }
2347        }
2348    }
2349}
2350
2351/**
2352 * All the information relevant to shortcuts from a single package (per-user).
2353 */
2354class PackageShortcuts {
2355    private static final String TAG = ShortcutService.TAG;
2356
2357    static final String TAG_ROOT = "package";
2358    private static final String TAG_INTENT_EXTRAS = "intent-extras";
2359    private static final String TAG_EXTRAS = "extras";
2360    private static final String TAG_SHORTCUT = "shortcut";
2361
2362    private static final String ATTR_NAME = "name";
2363    private static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
2364    private static final String ATTR_CALL_COUNT = "call-count";
2365    private static final String ATTR_LAST_RESET = "last-reset";
2366    private static final String ATTR_ID = "id";
2367    private static final String ATTR_ACTIVITY = "activity";
2368    private static final String ATTR_TITLE = "title";
2369    private static final String ATTR_INTENT = "intent";
2370    private static final String ATTR_WEIGHT = "weight";
2371    private static final String ATTR_TIMESTAMP = "timestamp";
2372    private static final String ATTR_FLAGS = "flags";
2373    private static final String ATTR_ICON_RES = "icon-res";
2374    private static final String ATTR_BITMAP_PATH = "bitmap-path";
2375
2376    @UserIdInt
2377    final int mUserId;
2378
2379    @NonNull
2380    final String mPackageName;
2381
2382    /**
2383     * All the shortcuts from the package, keyed on IDs.
2384     */
2385    final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
2386
2387    /**
2388     * # of dynamic shortcuts.
2389     */
2390    private int mDynamicShortcutCount = 0;
2391
2392    /**
2393     * # of times the package has called rate-limited APIs.
2394     */
2395    private int mApiCallCount;
2396
2397    /**
2398     * When {@link #mApiCallCount} was reset last time.
2399     */
2400    private long mLastResetTime;
2401
2402    PackageShortcuts(int userId, String packageName) {
2403        mUserId = userId;
2404        mPackageName = packageName;
2405    }
2406
2407    @Nullable
2408    public ShortcutInfo findShortcutById(String id) {
2409        return mShortcuts.get(id);
2410    }
2411
2412    private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
2413            @NonNull String id) {
2414        final ShortcutInfo shortcut = mShortcuts.remove(id);
2415        if (shortcut != null) {
2416            s.removeIcon(mUserId, shortcut);
2417            shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
2418        }
2419        return shortcut;
2420    }
2421
2422    void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
2423        deleteShortcut(s, newShortcut.getId());
2424        s.saveIconAndFixUpShortcut(mUserId, newShortcut);
2425        mShortcuts.put(newShortcut.getId(), newShortcut);
2426    }
2427
2428    /**
2429     * Add a shortcut, or update one with the same ID, with taking over existing flags.
2430     *
2431     * It checks the max number of dynamic shortcuts.
2432     */
2433    public void addDynamicShortcut(@NonNull ShortcutService s,
2434            @NonNull ShortcutInfo newShortcut) {
2435        newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
2436
2437        final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
2438
2439        final boolean wasPinned;
2440        final int newDynamicCount;
2441
2442        if (oldShortcut == null) {
2443            wasPinned = false;
2444            newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
2445        } else {
2446            wasPinned = oldShortcut.isPinned();
2447            if (oldShortcut.isDynamic()) {
2448                newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut.
2449            } else {
2450                newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
2451            }
2452        }
2453
2454        // Make sure there's still room.
2455        s.enforceMaxDynamicShortcuts(newDynamicCount);
2456
2457        // Okay, make it dynamic and add.
2458        if (wasPinned) {
2459            newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
2460        }
2461
2462        addShortcut(s, newShortcut);
2463        mDynamicShortcutCount = newDynamicCount;
2464    }
2465
2466    /**
2467     * Remove all shortcuts that aren't pinned nor dynamic.
2468     */
2469    private void removeOrphans(@NonNull ShortcutService s) {
2470        ArrayList<String> removeList = null; // Lazily initialize.
2471
2472        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
2473            final ShortcutInfo si = mShortcuts.valueAt(i);
2474
2475            if (si.isPinned() || si.isDynamic()) continue;
2476
2477            if (removeList == null) {
2478                removeList = new ArrayList<>();
2479            }
2480            removeList.add(si.getId());
2481        }
2482        if (removeList != null) {
2483            for (int i = removeList.size() - 1; i >= 0; i--) {
2484                deleteShortcut(s, removeList.get(i));
2485            }
2486        }
2487    }
2488
2489    /**
2490     * Remove all dynamic shortcuts.
2491     */
2492    public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
2493        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
2494            mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
2495        }
2496        removeOrphans(s);
2497        mDynamicShortcutCount = 0;
2498    }
2499
2500    /**
2501     * Remove a dynamic shortcut by ID.
2502     */
2503    public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
2504        final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
2505
2506        if (oldShortcut == null) {
2507            return;
2508        }
2509        if (oldShortcut.isDynamic()) {
2510            mDynamicShortcutCount--;
2511        }
2512        if (oldShortcut.isPinned()) {
2513            oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
2514        } else {
2515            deleteShortcut(s, shortcutId);
2516        }
2517    }
2518
2519    /**
2520     * Called after a launcher updates the pinned set.  For each shortcut in this package,
2521     * set FLAG_PINNED if any launcher has pinned it.  Otherwise, clear it.
2522     *
2523     * <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
2524     */
2525    public void refreshPinnedFlags(@NonNull ShortcutService s) {
2526        // First, un-pin all shortcuts
2527        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
2528            mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
2529        }
2530
2531        // Then, for the pinned set for each launcher, set the pin flag one by one.
2532        final ArrayMap<String, LauncherShortcuts> launchers =
2533                s.getUserShortcutsLocked(mUserId).getLaunchers();
2534
2535        for (int l = launchers.size() - 1; l >= 0; l--) {
2536            final LauncherShortcuts launcherShortcuts = launchers.valueAt(l);
2537            final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(mPackageName);
2538
2539            if (pinned == null || pinned.size() == 0) {
2540                continue;
2541            }
2542            for (int i = pinned.size() - 1; i >= 0; i--) {
2543                final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i));
2544                if (si == null) {
2545                    s.wtf("Shortcut not found");
2546                } else {
2547                    si.addFlags(ShortcutInfo.FLAG_PINNED);
2548                }
2549            }
2550        }
2551
2552        // Lastly, remove the ones that are no longer pinned nor dynamic.
2553        removeOrphans(s);
2554    }
2555
2556    /**
2557     * Number of calls that the caller has made, since the last reset.
2558     */
2559    public int getApiCallCount(@NonNull ShortcutService s) {
2560        final long last = s.getLastResetTimeLocked();
2561
2562        final long now = s.injectCurrentTimeMillis();
2563        if (ShortcutService.isClockValid(now) && mLastResetTime > now) {
2564            Slog.w(TAG, "Clock rewound");
2565            // Clock rewound.
2566            mLastResetTime = now;
2567            mApiCallCount = 0;
2568            return mApiCallCount;
2569        }
2570
2571        // If not reset yet, then reset.
2572        if (mLastResetTime < last) {
2573            if (ShortcutService.DEBUG) {
2574                Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting",
2575                        mLastResetTime, now, last));
2576            }
2577            mApiCallCount = 0;
2578            mLastResetTime = last;
2579        }
2580        return mApiCallCount;
2581    }
2582
2583    /**
2584     * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
2585     * and return true.  Otherwise just return false.
2586     */
2587    public boolean tryApiCall(@NonNull ShortcutService s) {
2588        if (getApiCallCount(s) >= s.mMaxDailyUpdates) {
2589            return false;
2590        }
2591        mApiCallCount++;
2592        return true;
2593    }
2594
2595    public void resetRateLimitingForCommandLine() {
2596        mApiCallCount = 0;
2597        mLastResetTime = 0;
2598    }
2599
2600    /**
2601     * Find all shortcuts that match {@code query}.
2602     */
2603    public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
2604            @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
2605            @Nullable String callingLauncher) {
2606
2607        // Set of pinned shortcuts by the calling launcher.
2608        final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
2609                : s.getLauncherShortcuts(callingLauncher, mUserId)
2610                    .getPinnedShortcutIds(mPackageName);
2611
2612        for (int i = 0; i < mShortcuts.size(); i++) {
2613            final ShortcutInfo si = mShortcuts.valueAt(i);
2614
2615            // If it's called by non-launcher (i.e. publisher, always include -> true.
2616            // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned
2617            // it.
2618            final boolean isPinnedByCaller = (callingLauncher == null)
2619                    || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
2620            if (!si.isDynamic()) {
2621                if (!si.isPinned()) {
2622                    s.wtf("Shortcut not pinned here");
2623                    continue;
2624                }
2625                if (!isPinnedByCaller) {
2626                    continue;
2627                }
2628            }
2629            final ShortcutInfo clone = si.clone(cloneFlag);
2630            // Fix up isPinned for the caller.  Note we need to do it before the "test" callback,
2631            // since it may check isPinned.
2632            if (!isPinnedByCaller) {
2633                clone.clearFlags(ShortcutInfo.FLAG_PINNED);
2634            }
2635            if (query == null || query.test(clone)) {
2636                result.add(clone);
2637            }
2638        }
2639    }
2640
2641    public void resetThrottling() {
2642        mApiCallCount = 0;
2643    }
2644
2645    public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
2646        pw.println();
2647
2648        pw.print(prefix);
2649        pw.print("Package: ");
2650        pw.print(mPackageName);
2651        pw.println();
2652
2653        pw.print(prefix);
2654        pw.print("  ");
2655        pw.print("Calls: ");
2656        pw.print(getApiCallCount(s));
2657        pw.println();
2658
2659        // This should be after getApiCallCount(), which may update it.
2660        pw.print(prefix);
2661        pw.print("  ");
2662        pw.print("Last reset: [");
2663        pw.print(mLastResetTime);
2664        pw.print("] ");
2665        pw.print(s.formatTime(mLastResetTime));
2666        pw.println();
2667
2668        pw.println("      Shortcuts:");
2669        long totalBitmapSize = 0;
2670        final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
2671        final int size = shortcuts.size();
2672        for (int i = 0; i < size; i++) {
2673            final ShortcutInfo si = shortcuts.valueAt(i);
2674            pw.print("        ");
2675            pw.println(si.toInsecureString());
2676            if (si.getBitmapPath() != null) {
2677                final long len = new File(si.getBitmapPath()).length();
2678                pw.print("          ");
2679                pw.print("bitmap size=");
2680                pw.println(len);
2681
2682                totalBitmapSize += len;
2683            }
2684        }
2685        pw.print(prefix);
2686        pw.print("  ");
2687        pw.print("Total bitmap size: ");
2688        pw.print(totalBitmapSize);
2689        pw.print(" (");
2690        pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize));
2691        pw.println(")");
2692    }
2693
2694    public void saveToXml(@NonNull XmlSerializer out) throws IOException, XmlPullParserException {
2695        final int size = mShortcuts.size();
2696
2697        if (size == 0 && mApiCallCount == 0) {
2698            return; // nothing to write.
2699        }
2700
2701        out.startTag(null, TAG_ROOT);
2702
2703        ShortcutService.writeAttr(out, ATTR_NAME, mPackageName);
2704        ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
2705        ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
2706        ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
2707
2708        for (int j = 0; j < size; j++) {
2709            saveShortcut(out, mShortcuts.valueAt(j));
2710        }
2711
2712        out.endTag(null, TAG_ROOT);
2713    }
2714
2715    private static void saveShortcut(XmlSerializer out, ShortcutInfo si)
2716            throws IOException, XmlPullParserException {
2717        out.startTag(null, TAG_SHORTCUT);
2718        ShortcutService.writeAttr(out, ATTR_ID, si.getId());
2719        // writeAttr(out, "package", si.getPackageName()); // not needed
2720        ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
2721        // writeAttr(out, "icon", si.getIcon());  // We don't save it.
2722        ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
2723        ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
2724        ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight());
2725        ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
2726                si.getLastChangedTimestamp());
2727        ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
2728        ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId());
2729        ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
2730
2731        ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS,
2732                si.getIntentPersistableExtras());
2733        ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
2734
2735        out.endTag(null, TAG_SHORTCUT);
2736    }
2737
2738    public static PackageShortcuts loadFromXml(XmlPullParser parser, int userId)
2739            throws IOException, XmlPullParserException {
2740
2741        final String packageName = ShortcutService.parseStringAttribute(parser,
2742                ATTR_NAME);
2743
2744        final PackageShortcuts ret = new PackageShortcuts(userId, packageName);
2745
2746        ret.mDynamicShortcutCount =
2747                ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
2748        ret.mApiCallCount =
2749                ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
2750        ret.mLastResetTime =
2751                ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
2752
2753        final int outerDepth = parser.getDepth();
2754        int type;
2755        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
2756                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
2757            if (type != XmlPullParser.START_TAG) {
2758                continue;
2759            }
2760            final int depth = parser.getDepth();
2761            final String tag = parser.getName();
2762            switch (tag) {
2763                case TAG_SHORTCUT:
2764                    final ShortcutInfo si = parseShortcut(parser, packageName);
2765
2766                    // Don't use addShortcut(), we don't need to save the icon.
2767                    ret.mShortcuts.put(si.getId(), si);
2768                    continue;
2769            }
2770            throw ShortcutService.throwForInvalidTag(depth, tag);
2771        }
2772        return ret;
2773    }
2774
2775    private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName)
2776            throws IOException, XmlPullParserException {
2777        String id;
2778        ComponentName activityComponent;
2779        // Icon icon;
2780        String title;
2781        Intent intent;
2782        PersistableBundle intentPersistableExtras = null;
2783        int weight;
2784        PersistableBundle extras = null;
2785        long lastChangedTimestamp;
2786        int flags;
2787        int iconRes;
2788        String bitmapPath;
2789
2790        id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
2791        activityComponent = ShortcutService.parseComponentNameAttribute(parser,
2792                ATTR_ACTIVITY);
2793        title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
2794        intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
2795        weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT);
2796        lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser,
2797                ATTR_TIMESTAMP);
2798        flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
2799        iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES);
2800        bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
2801
2802        final int outerDepth = parser.getDepth();
2803        int type;
2804        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
2805                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
2806            if (type != XmlPullParser.START_TAG) {
2807                continue;
2808            }
2809            final int depth = parser.getDepth();
2810            final String tag = parser.getName();
2811            if (ShortcutService.DEBUG_LOAD) {
2812                Slog.d(TAG, String.format("  depth=%d type=%d name=%s",
2813                        depth, type, tag));
2814            }
2815            switch (tag) {
2816                case TAG_INTENT_EXTRAS:
2817                    intentPersistableExtras = PersistableBundle.restoreFromXml(parser);
2818                    continue;
2819                case TAG_EXTRAS:
2820                    extras = PersistableBundle.restoreFromXml(parser);
2821                    continue;
2822            }
2823            throw ShortcutService.throwForInvalidTag(depth, tag);
2824        }
2825        return new ShortcutInfo(
2826                id, packageName, activityComponent, /* icon =*/ null, title, intent,
2827                intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
2828                iconRes, bitmapPath);
2829    }
2830}
2831