ShortcutService.java revision 0acbb14574d859b5f1cc0b7c6bbdfbeba38f3e55
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.app.AppGlobals;
23import android.content.ComponentName;
24import android.content.ContentProvider;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.IPackageManager;
29import android.content.pm.IShortcutService;
30import android.content.pm.LauncherApps;
31import android.content.pm.LauncherApps.ShortcutQuery;
32import android.content.pm.PackageInfo;
33import android.content.pm.PackageManager;
34import android.content.pm.PackageManager.NameNotFoundException;
35import android.content.pm.PackageManagerInternal;
36import android.content.pm.ParceledListSlice;
37import android.content.pm.ResolveInfo;
38import android.content.pm.ShortcutInfo;
39import android.content.pm.ShortcutServiceInternal;
40import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
41import android.graphics.Bitmap;
42import android.graphics.Bitmap.CompressFormat;
43import android.graphics.BitmapFactory;
44import android.graphics.Canvas;
45import android.graphics.RectF;
46import android.graphics.drawable.Icon;
47import android.net.Uri;
48import android.os.Binder;
49import android.os.Environment;
50import android.os.Handler;
51import android.os.Looper;
52import android.os.ParcelFileDescriptor;
53import android.os.PersistableBundle;
54import android.os.Process;
55import android.os.RemoteException;
56import android.os.ResultReceiver;
57import android.os.SELinux;
58import android.os.ShellCommand;
59import android.os.UserHandle;
60import android.os.UserManager;
61import android.text.TextUtils;
62import android.text.format.Time;
63import android.util.ArrayMap;
64import android.util.ArraySet;
65import android.util.AtomicFile;
66import android.util.KeyValueListParser;
67import android.util.Slog;
68import android.util.SparseArray;
69import android.util.TypedValue;
70import android.util.Xml;
71
72import com.android.internal.annotations.GuardedBy;
73import com.android.internal.annotations.VisibleForTesting;
74import com.android.internal.content.PackageMonitor;
75import com.android.internal.os.BackgroundThread;
76import com.android.internal.util.ArrayUtils;
77import com.android.internal.util.FastXmlSerializer;
78import com.android.internal.util.Preconditions;
79import com.android.server.LocalServices;
80import com.android.server.SystemService;
81
82import libcore.io.IoUtils;
83
84import org.xmlpull.v1.XmlPullParser;
85import org.xmlpull.v1.XmlPullParserException;
86import org.xmlpull.v1.XmlSerializer;
87
88import java.io.File;
89import java.io.FileDescriptor;
90import java.io.FileInputStream;
91import java.io.FileNotFoundException;
92import java.io.FileOutputStream;
93import java.io.IOException;
94import java.io.InputStream;
95import java.io.PrintWriter;
96import java.net.URISyntaxException;
97import java.nio.charset.StandardCharsets;
98import java.util.ArrayList;
99import java.util.List;
100import java.util.function.Predicate;
101
102/**
103 * TODO:
104 *
105 * - Default launcher check does take a few ms.  Worth caching.
106 *
107 * - Allow non-default launcher to start pinned shortcuts. (but not dynamic.)
108 *
109 * - Extract the user/package/launcher classes to their own files.  Maybe rename so they all have
110 *   the same "Shortcut" prefix.
111 *
112 * - Listen to PACKAGE_*, remove orphan info, update timestamp for icon res
113 *   -> Need to scan all packages when a user starts too.
114 *   -> Clear data -> remove all dynamic?  but not the pinned?
115 *
116 * - Scan and remove orphan bitmaps (just in case).
117 *
118 * - Backup & restore
119 *
120 * - Detect when already registered instances are passed to APIs again, which might break
121 *   internal bitmap handling.
122 */
123public class ShortcutService extends IShortcutService.Stub {
124    static final String TAG = "ShortcutService";
125
126    static final boolean DEBUG = false; // STOPSHIP if true
127    static final boolean DEBUG_LOAD = false; // STOPSHIP if true
128    static final boolean ENABLE_DEBUG_COMMAND = true; // STOPSHIP if true
129
130    @VisibleForTesting
131    static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
132
133    @VisibleForTesting
134    static final int DEFAULT_MAX_DAILY_UPDATES = 10;
135
136    @VisibleForTesting
137    static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
138
139    @VisibleForTesting
140    static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
141
142    @VisibleForTesting
143    static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
144
145    @VisibleForTesting
146    static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
147
148    @VisibleForTesting
149    static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
150
151    @VisibleForTesting
152    static final int DEFAULT_SAVE_DELAY_MS = 3000;
153
154    @VisibleForTesting
155    static final String FILENAME_BASE_STATE = "shortcut_service.xml";
156
157    @VisibleForTesting
158    static final String DIRECTORY_PER_USER = "shortcut_service";
159
160    @VisibleForTesting
161    static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
162
163    static final String DIRECTORY_BITMAPS = "bitmaps";
164
165    private static final String TAG_ROOT = "root";
166    private static final String TAG_LAST_RESET_TIME = "last_reset_time";
167
168    private static final String ATTR_VALUE = "value";
169
170    @VisibleForTesting
171    interface ConfigConstants {
172        /**
173         * Key name for the save delay, in milliseconds. (int)
174         */
175        String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
176
177        /**
178         * Key name for the throttling reset interval, in seconds. (long)
179         */
180        String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
181
182        /**
183         * Key name for the max number of modifying API calls per app for every interval. (int)
184         */
185        String KEY_MAX_DAILY_UPDATES = "max_daily_updates";
186
187        /**
188         * Key name for the max icon dimensions in DP, for non-low-memory devices.
189         */
190        String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
191
192        /**
193         * Key name for the max icon dimensions in DP, for low-memory devices.
194         */
195        String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
196
197        /**
198         * Key name for the max dynamic shortcuts per app. (int)
199         */
200        String KEY_MAX_SHORTCUTS = "max_shortcuts";
201
202        /**
203         * Key name for icon compression quality, 0-100.
204         */
205        String KEY_ICON_QUALITY = "icon_quality";
206
207        /**
208         * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
209         */
210        String KEY_ICON_FORMAT = "icon_format";
211    }
212
213    final Context mContext;
214
215    private final Object mLock = new Object();
216
217    private final Handler mHandler;
218
219    @GuardedBy("mLock")
220    private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
221
222    @GuardedBy("mLock")
223    private long mRawLastResetTime;
224
225    /**
226     * User ID -> UserShortcuts
227     */
228    @GuardedBy("mLock")
229    private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
230
231    /**
232     * Max number of dynamic shortcuts that each application can have at a time.
233     */
234    private int mMaxDynamicShortcuts;
235
236    /**
237     * Max number of updating API calls that each application can make a day.
238     */
239    int mMaxDailyUpdates;
240
241    /**
242     * Actual throttling-reset interval.  By default it's a day.
243     */
244    private long mResetInterval;
245
246    /**
247     * Icon max width/height in pixels.
248     */
249    private int mMaxIconDimension;
250
251    private CompressFormat mIconPersistFormat;
252    private int mIconPersistQuality;
253
254    private int mSaveDelayMillis;
255
256    private final IPackageManager mIPackageManager;
257    private final PackageManagerInternal mPackageManagerInternal;
258    private final UserManager mUserManager;
259
260    @GuardedBy("mLock")
261    private List<Integer> mDirtyUserIds = new ArrayList<>();
262
263    public ShortcutService(Context context) {
264        this(context, BackgroundThread.get().getLooper());
265    }
266
267    @VisibleForTesting
268    ShortcutService(Context context, Looper looper) {
269        mContext = Preconditions.checkNotNull(context);
270        LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
271        mHandler = new Handler(looper);
272        mIPackageManager = AppGlobals.getPackageManager();
273        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
274        mUserManager = context.getSystemService(UserManager.class);
275
276        mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
277    }
278
279    /**
280     * System service lifecycle.
281     */
282    public static final class Lifecycle extends SystemService {
283        final ShortcutService mService;
284
285        public Lifecycle(Context context) {
286            super(context);
287            mService = new ShortcutService(context);
288        }
289
290        @Override
291        public void onStart() {
292            publishBinderService(Context.SHORTCUT_SERVICE, mService);
293        }
294
295        @Override
296        public void onBootPhase(int phase) {
297            mService.onBootPhase(phase);
298        }
299
300        @Override
301        public void onCleanupUser(int userHandle) {
302            mService.handleCleanupUser(userHandle);
303        }
304
305        @Override
306        public void onUnlockUser(int userId) {
307            mService.handleUnlockUser(userId);
308        }
309    }
310
311    /** lifecycle event */
312    void onBootPhase(int phase) {
313        if (DEBUG) {
314            Slog.d(TAG, "onBootPhase: " + phase);
315        }
316        switch (phase) {
317            case SystemService.PHASE_LOCK_SETTINGS_READY:
318                initialize();
319                break;
320        }
321    }
322
323    /** lifecycle event */
324    void handleUnlockUser(int userId) {
325        synchronized (mLock) {
326            // Preload
327            getUserShortcutsLocked(userId);
328
329            cleanupGonePackages(userId);
330        }
331    }
332
333    /** lifecycle event */
334    void handleCleanupUser(int userId) {
335        synchronized (mLock) {
336            unloadUserLocked(userId);
337        }
338    }
339
340    private void unloadUserLocked(int userId) {
341        if (DEBUG) {
342            Slog.d(TAG, "unloadUserLocked: user=" + userId);
343        }
344        // Save all dirty information.
345        saveDirtyInfo();
346
347        // Unload
348        mUsers.delete(userId);
349    }
350
351    /** Return the base state file name */
352    private AtomicFile getBaseStateFile() {
353        final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
354        path.mkdirs();
355        return new AtomicFile(path);
356    }
357
358    /**
359     * Init the instance. (load the state file, etc)
360     */
361    private void initialize() {
362        synchronized (mLock) {
363            loadConfigurationLocked();
364            loadBaseStateLocked();
365        }
366    }
367
368    /**
369     * Load the configuration from Settings.
370     */
371    private void loadConfigurationLocked() {
372        updateConfigurationLocked(injectShortcutManagerConstants());
373    }
374
375    /**
376     * Load the configuration from Settings.
377     */
378    @VisibleForTesting
379    boolean updateConfigurationLocked(String config) {
380        boolean result = true;
381
382        final KeyValueListParser parser = new KeyValueListParser(',');
383        try {
384            parser.setString(config);
385        } catch (IllegalArgumentException e) {
386            // Failed to parse the settings string, log this and move on
387            // with defaults.
388            Slog.e(TAG, "Bad shortcut manager settings", e);
389            result = false;
390        }
391
392        mSaveDelayMillis = (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
393                DEFAULT_SAVE_DELAY_MS);
394
395        mResetInterval = parser.getLong(
396                ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
397                * 1000L;
398
399        mMaxDailyUpdates = (int) parser.getLong(
400                ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES);
401
402        mMaxDynamicShortcuts = (int) parser.getLong(
403                ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP);
404
405        final int iconDimensionDp = injectIsLowRamDevice()
406                ? (int) parser.getLong(
407                    ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
408                    DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
409                : (int) parser.getLong(
410                    ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
411                    DEFAULT_MAX_ICON_DIMENSION_DP);
412
413        mMaxIconDimension = injectDipToPixel(iconDimensionDp);
414
415        mIconPersistFormat = CompressFormat.valueOf(
416                parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
417
418        mIconPersistQuality = (int) parser.getLong(
419                ConfigConstants.KEY_ICON_QUALITY,
420                DEFAULT_ICON_PERSIST_QUALITY);
421
422        return result;
423    }
424
425    @VisibleForTesting
426    String injectShortcutManagerConstants() {
427        return android.provider.Settings.Global.getString(
428                mContext.getContentResolver(),
429                android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
430    }
431
432    @VisibleForTesting
433    int injectDipToPixel(int dip) {
434        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
435                mContext.getResources().getDisplayMetrics());
436    }
437
438    // === Persisting ===
439
440    @Nullable
441    static String parseStringAttribute(XmlPullParser parser, String attribute) {
442        return parser.getAttributeValue(null, attribute);
443    }
444
445    static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) {
446        return parseLongAttribute(parser, attribute) == 1;
447    }
448
449    static int parseIntAttribute(XmlPullParser parser, String attribute) {
450        return (int) parseLongAttribute(parser, attribute);
451    }
452
453    static long parseLongAttribute(XmlPullParser parser, String attribute) {
454        final String value = parseStringAttribute(parser, attribute);
455        if (TextUtils.isEmpty(value)) {
456            return 0;
457        }
458        try {
459            return Long.parseLong(value);
460        } catch (NumberFormatException e) {
461            Slog.e(TAG, "Error parsing long " + value);
462            return 0;
463        }
464    }
465
466    @Nullable
467    static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
468        final String value = parseStringAttribute(parser, attribute);
469        if (TextUtils.isEmpty(value)) {
470            return null;
471        }
472        return ComponentName.unflattenFromString(value);
473    }
474
475    @Nullable
476    static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
477        final String value = parseStringAttribute(parser, attribute);
478        if (TextUtils.isEmpty(value)) {
479            return null;
480        }
481        try {
482            return Intent.parseUri(value, /* flags =*/ 0);
483        } catch (URISyntaxException e) {
484            Slog.e(TAG, "Error parsing intent", e);
485            return null;
486        }
487    }
488
489    static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
490        if (TextUtils.isEmpty(value)) return;
491
492        out.startTag(null, tag);
493        out.attribute(null, ATTR_VALUE, value);
494        out.endTag(null, tag);
495    }
496
497    static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
498        writeTagValue(out, tag, Long.toString(value));
499    }
500
501    static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
502        if (name == null) return;
503        writeTagValue(out, tag, name.flattenToString());
504    }
505
506    static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
507            throws IOException, XmlPullParserException {
508        if (bundle == null) return;
509
510        out.startTag(null, tag);
511        bundle.saveToXml(out);
512        out.endTag(null, tag);
513    }
514
515    static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
516        if (TextUtils.isEmpty(value)) return;
517
518        out.attribute(null, name, value);
519    }
520
521    static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
522        writeAttr(out, name, String.valueOf(value));
523    }
524
525    static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
526        if (value) {
527            writeAttr(out, name, "1");
528        }
529    }
530
531    static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
532        if (comp == null) return;
533        writeAttr(out, name, comp.flattenToString());
534    }
535
536    static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
537        if (intent == null) return;
538
539        writeAttr(out, name, intent.toUri(/* flags =*/ 0));
540    }
541
542    @VisibleForTesting
543    void saveBaseStateLocked() {
544        final AtomicFile file = getBaseStateFile();
545        if (DEBUG) {
546            Slog.d(TAG, "Saving to " + file.getBaseFile());
547        }
548
549        FileOutputStream outs = null;
550        try {
551            outs = file.startWrite();
552
553            // Write to XML
554            XmlSerializer out = new FastXmlSerializer();
555            out.setOutput(outs, StandardCharsets.UTF_8.name());
556            out.startDocument(null, true);
557            out.startTag(null, TAG_ROOT);
558
559            // Body.
560            writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
561
562            // Epilogue.
563            out.endTag(null, TAG_ROOT);
564            out.endDocument();
565
566            // Close.
567            file.finishWrite(outs);
568        } catch (IOException e) {
569            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
570            file.failWrite(outs);
571        }
572    }
573
574    private void loadBaseStateLocked() {
575        mRawLastResetTime = 0;
576
577        final AtomicFile file = getBaseStateFile();
578        if (DEBUG) {
579            Slog.d(TAG, "Loading from " + file.getBaseFile());
580        }
581        try (FileInputStream in = file.openRead()) {
582            XmlPullParser parser = Xml.newPullParser();
583            parser.setInput(in, StandardCharsets.UTF_8.name());
584
585            int type;
586            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
587                if (type != XmlPullParser.START_TAG) {
588                    continue;
589                }
590                final int depth = parser.getDepth();
591                // Check the root tag
592                final String tag = parser.getName();
593                if (depth == 1) {
594                    if (!TAG_ROOT.equals(tag)) {
595                        Slog.e(TAG, "Invalid root tag: " + tag);
596                        return;
597                    }
598                    continue;
599                }
600                // Assume depth == 2
601                switch (tag) {
602                    case TAG_LAST_RESET_TIME:
603                        mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
604                        break;
605                    default:
606                        Slog.e(TAG, "Invalid tag: " + tag);
607                        break;
608                }
609            }
610        } catch (FileNotFoundException e) {
611            // Use the default
612        } catch (IOException|XmlPullParserException e) {
613            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
614
615            mRawLastResetTime = 0;
616        }
617        // Adjust the last reset time.
618        getLastResetTimeLocked();
619    }
620
621    private void saveUserLocked(@UserIdInt int userId) {
622        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
623        if (DEBUG) {
624            Slog.d(TAG, "Saving to " + path);
625        }
626        path.mkdirs();
627        final AtomicFile file = new AtomicFile(path);
628        FileOutputStream outs = null;
629        try {
630            outs = file.startWrite();
631
632            // Write to XML
633            XmlSerializer out = new FastXmlSerializer();
634            out.setOutput(outs, StandardCharsets.UTF_8.name());
635            out.startDocument(null, true);
636
637            getUserShortcutsLocked(userId).saveToXml(this, out, /* forBackup= */ false);
638
639            out.endDocument();
640
641            // Close.
642            file.finishWrite(outs);
643        } catch (IOException|XmlPullParserException e) {
644            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
645            file.failWrite(outs);
646        }
647    }
648
649    static IOException throwForInvalidTag(int depth, String tag) throws IOException {
650        throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
651    }
652
653    @Nullable
654    private ShortcutUser loadUserLocked(@UserIdInt int userId) {
655        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
656        if (DEBUG) {
657            Slog.d(TAG, "Loading from " + path);
658        }
659        final AtomicFile file = new AtomicFile(path);
660
661        final FileInputStream in;
662        try {
663            in = file.openRead();
664        } catch (FileNotFoundException e) {
665            if (DEBUG) {
666                Slog.d(TAG, "Not found " + path);
667            }
668            return null;
669        }
670        ShortcutUser ret = null;
671        try {
672            XmlPullParser parser = Xml.newPullParser();
673            parser.setInput(in, StandardCharsets.UTF_8.name());
674
675            int type;
676            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
677                if (type != XmlPullParser.START_TAG) {
678                    continue;
679                }
680                final int depth = parser.getDepth();
681
682                final String tag = parser.getName();
683                if (DEBUG_LOAD) {
684                    Slog.d(TAG, String.format("depth=%d type=%d name=%s",
685                            depth, type, tag));
686                }
687                if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
688                    ret = ShortcutUser.loadFromXml(parser, userId);
689                    continue;
690                }
691                throwForInvalidTag(depth, tag);
692            }
693            return ret;
694        } catch (IOException|XmlPullParserException e) {
695            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
696            return null;
697        } finally {
698            IoUtils.closeQuietly(in);
699        }
700    }
701
702    private void scheduleSaveBaseState() {
703        scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
704    }
705
706    void scheduleSaveUser(@UserIdInt int userId) {
707        scheduleSaveInner(userId);
708    }
709
710    // In order to re-schedule, we need to reuse the same instance, so keep it in final.
711    private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo;
712
713    private void scheduleSaveInner(@UserIdInt int userId) {
714        if (DEBUG) {
715            Slog.d(TAG, "Scheduling to save for " + userId);
716        }
717        synchronized (mLock) {
718            if (!mDirtyUserIds.contains(userId)) {
719                mDirtyUserIds.add(userId);
720            }
721        }
722        // If already scheduled, remove that and re-schedule in N seconds.
723        mHandler.removeCallbacks(mSaveDirtyInfoRunner);
724        mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
725    }
726
727    @VisibleForTesting
728    void saveDirtyInfo() {
729        if (DEBUG) {
730            Slog.d(TAG, "saveDirtyInfo");
731        }
732        synchronized (mLock) {
733            for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
734                final int userId = mDirtyUserIds.get(i);
735                if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
736                    saveBaseStateLocked();
737                } else {
738                    saveUserLocked(userId);
739                }
740            }
741            mDirtyUserIds.clear();
742        }
743    }
744
745    /** Return the last reset time. */
746    long getLastResetTimeLocked() {
747        updateTimesLocked();
748        return mRawLastResetTime;
749    }
750
751    /** Return the next reset time. */
752    long getNextResetTimeLocked() {
753        updateTimesLocked();
754        return mRawLastResetTime + mResetInterval;
755    }
756
757    static boolean isClockValid(long time) {
758        return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
759    }
760
761    /**
762     * Update the last reset time.
763     */
764    private void updateTimesLocked() {
765
766        final long now = injectCurrentTimeMillis();
767
768        final long prevLastResetTime = mRawLastResetTime;
769
770        if (mRawLastResetTime == 0) { // first launch.
771            // TODO Randomize??
772            mRawLastResetTime = now;
773        } else if (now < mRawLastResetTime) {
774            // Clock rewound.
775            if (isClockValid(now)) {
776                Slog.w(TAG, "Clock rewound");
777                // TODO Randomize??
778                mRawLastResetTime = now;
779            }
780        } else {
781            if ((mRawLastResetTime + mResetInterval) <= now) {
782                final long offset = mRawLastResetTime % mResetInterval;
783                mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
784            }
785        }
786        if (prevLastResetTime != mRawLastResetTime) {
787            scheduleSaveBaseState();
788        }
789    }
790
791    @GuardedBy("mLock")
792    @NonNull
793    boolean isUserLoadedLocked(@UserIdInt int userId) {
794        return mUsers.get(userId) != null;
795    }
796
797    /** Return the per-user state. */
798    @GuardedBy("mLock")
799    @NonNull
800    ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
801        ShortcutUser userPackages = mUsers.get(userId);
802        if (userPackages == null) {
803            userPackages = loadUserLocked(userId);
804            if (userPackages == null) {
805                userPackages = new ShortcutUser(userId);
806            }
807            mUsers.put(userId, userPackages);
808        }
809        return userPackages;
810    }
811
812    /** Return the per-user per-package state. */
813    @GuardedBy("mLock")
814    @NonNull
815    ShortcutPackage getPackageShortcutsLocked(
816            @NonNull String packageName, @UserIdInt int userId) {
817        return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
818    }
819
820    @GuardedBy("mLock")
821    @NonNull
822    ShortcutLauncher getLauncherShortcuts(
823            @NonNull String packageName, @UserIdInt int userId) {
824        return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName);
825    }
826
827    // === Caller validation ===
828
829    void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
830        if (shortcut.getBitmapPath() != null) {
831            if (DEBUG) {
832                Slog.d(TAG, "Removing " + shortcut.getBitmapPath());
833            }
834            new File(shortcut.getBitmapPath()).delete();
835
836            shortcut.setBitmapPath(null);
837            shortcut.setIconResourceId(0);
838            shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
839        }
840    }
841
842    @VisibleForTesting
843    static class FileOutputStreamWithPath extends FileOutputStream {
844        private final File mFile;
845
846        public FileOutputStreamWithPath(File file) throws FileNotFoundException {
847            super(file);
848            mFile = file;
849        }
850
851        public File getFile() {
852            return mFile;
853        }
854    }
855
856    /**
857     * Build the cached bitmap filename for a shortcut icon.
858     *
859     * The filename will be based on the ID, except certain characters will be escaped.
860     */
861    @VisibleForTesting
862    FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
863            throws IOException {
864        final File packagePath = new File(getUserBitmapFilePath(userId),
865                shortcut.getPackageName());
866        if (!packagePath.isDirectory()) {
867            packagePath.mkdirs();
868            if (!packagePath.isDirectory()) {
869                throw new IOException("Unable to create directory " + packagePath);
870            }
871            SELinux.restorecon(packagePath);
872        }
873
874        final String baseName = String.valueOf(injectCurrentTimeMillis());
875        for (int suffix = 0;; suffix++) {
876            final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
877            final File file = new File(packagePath, filename);
878            if (!file.exists()) {
879                if (DEBUG) {
880                    Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
881                }
882                return new FileOutputStreamWithPath(file);
883            }
884        }
885    }
886
887    void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) {
888        if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
889            return;
890        }
891
892        final long token = injectClearCallingIdentity();
893        try {
894            // Clear icon info on the shortcut.
895            shortcut.setIconResourceId(0);
896            shortcut.setBitmapPath(null);
897
898            final Icon icon = shortcut.getIcon();
899            if (icon == null) {
900                return; // has no icon
901            }
902
903            Bitmap bitmap = null;
904            try {
905                switch (icon.getType()) {
906                    case Icon.TYPE_RESOURCE: {
907                        injectValidateIconResPackage(shortcut, icon);
908
909                        shortcut.setIconResourceId(icon.getResId());
910                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
911                        return;
912                    }
913                    case Icon.TYPE_BITMAP: {
914                        bitmap = icon.getBitmap();
915                        break;
916                    }
917                    case Icon.TYPE_URI: {
918                        final Uri uri = ContentProvider.maybeAddUserId(icon.getUri(), userId);
919
920                        try (InputStream is = mContext.getContentResolver().openInputStream(uri)) {
921
922                            bitmap = BitmapFactory.decodeStream(is);
923
924                        } catch (IOException e) {
925                            Slog.e(TAG, "Unable to load icon from " + uri);
926                            return;
927                        }
928                        break;
929                    }
930                    default:
931                        // This shouldn't happen because we've already validated the icon, but
932                        // just in case.
933                        throw ShortcutInfo.getInvalidIconException();
934                }
935                if (bitmap == null) {
936                    Slog.e(TAG, "Null bitmap detected");
937                    return;
938                }
939                // Shrink and write to the file.
940                File path = null;
941                try {
942                    final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut);
943                    try {
944                        path = out.getFile();
945
946                        shrinkBitmap(bitmap, mMaxIconDimension)
947                                .compress(mIconPersistFormat, mIconPersistQuality, out);
948
949                        shortcut.setBitmapPath(out.getFile().getAbsolutePath());
950                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
951                    } finally {
952                        IoUtils.closeQuietly(out);
953                    }
954                } catch (IOException|RuntimeException e) {
955                    // STOPSHIP Change wtf to e
956                    Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
957                    if (path != null && path.exists()) {
958                        path.delete();
959                    }
960                }
961            } finally {
962                if (bitmap != null) {
963                    bitmap.recycle();
964                }
965                // Once saved, we won't use the original icon information, so null it out.
966                shortcut.clearIcon();
967            }
968        } finally {
969            injectRestoreCallingIdentity(token);
970        }
971    }
972
973    // Unfortunately we can't do this check in unit tests because we fake creator package names,
974    // so override in unit tests.
975    // TODO CTS this case.
976    void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
977        if (!shortcut.getPackageName().equals(icon.getResPackage())) {
978            throw new IllegalArgumentException(
979                    "Icon resource must reside in shortcut owner package");
980        }
981    }
982
983    @VisibleForTesting
984    static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
985        // Original width/height.
986        final int ow = in.getWidth();
987        final int oh = in.getHeight();
988        if ((ow <= maxSize) && (oh <= maxSize)) {
989            if (DEBUG) {
990                Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
991            }
992            return in;
993        }
994        final int longerDimension = Math.max(ow, oh);
995
996        // New width and height.
997        final int nw = ow * maxSize / longerDimension;
998        final int nh = oh * maxSize / longerDimension;
999        if (DEBUG) {
1000            Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
1001                    ow, oh, nw, nh));
1002        }
1003
1004        final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
1005        final Canvas c = new Canvas(scaledBitmap);
1006
1007        final RectF dst = new RectF(0, 0, nw, nh);
1008
1009        c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
1010
1011        in.recycle();
1012
1013        return scaledBitmap;
1014    }
1015
1016    // === Caller validation ===
1017
1018    private boolean isCallerSystem() {
1019        final int callingUid = injectBinderCallingUid();
1020         return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
1021    }
1022
1023    private boolean isCallerShell() {
1024        final int callingUid = injectBinderCallingUid();
1025        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
1026    }
1027
1028    private void enforceSystemOrShell() {
1029        Preconditions.checkState(isCallerSystem() || isCallerShell(),
1030                "Caller must be system or shell");
1031    }
1032
1033    private void enforceShell() {
1034        Preconditions.checkState(isCallerShell(), "Caller must be shell");
1035    }
1036
1037    private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
1038        Preconditions.checkStringNotEmpty(packageName, "packageName");
1039
1040        if (isCallerSystem()) {
1041            return; // no check
1042        }
1043
1044        final int callingUid = injectBinderCallingUid();
1045
1046        // Otherwise, make sure the arguments are valid.
1047        if (UserHandle.getUserId(callingUid) != userId) {
1048            throw new SecurityException("Invalid user-ID");
1049        }
1050        if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) {
1051            return; // Caller is valid.
1052        }
1053        throw new SecurityException("Caller UID= doesn't own " + packageName);
1054    }
1055
1056    // Test overrides it.
1057    int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
1058        try {
1059            return mContext.getPackageManager().getPackageUidAsUser(packageName,
1060                    PackageManager.MATCH_DIRECT_BOOT_AWARE
1061                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
1062                            | PackageManager.MATCH_UNINSTALLED_PACKAGES,
1063                    userId);
1064        } catch (NameNotFoundException e) {
1065            return -1;
1066        }
1067    }
1068
1069    void postToHandler(Runnable r) {
1070        mHandler.post(r);
1071    }
1072
1073    /**
1074     * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
1075     */
1076    void enforceMaxDynamicShortcuts(int numShortcuts) {
1077        if (numShortcuts > mMaxDynamicShortcuts) {
1078            throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
1079        }
1080    }
1081
1082    /**
1083     * - Sends a notification to LauncherApps
1084     * - Write to file
1085     */
1086    private void userPackageChanged(@NonNull String packageName, @UserIdInt int userId) {
1087        notifyListeners(packageName, userId);
1088        scheduleSaveUser(userId);
1089    }
1090
1091    private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
1092        if (!mUserManager.isUserRunning(userId)) {
1093            return;
1094        }
1095        postToHandler(() -> {
1096            final ArrayList<ShortcutChangeListener> copy;
1097            synchronized (mLock) {
1098                copy = new ArrayList<>(mListeners);
1099            }
1100            // Note onShortcutChanged() needs to be called with the system service permissions.
1101            for (int i = copy.size() - 1; i >= 0; i--) {
1102                copy.get(i).onShortcutChanged(packageName, userId);
1103            }
1104        });
1105    }
1106
1107    /**
1108     * Clean up / validate an incoming shortcut.
1109     * - Make sure all mandatory fields are set.
1110     * - Make sure the intent's extras are persistable, and them to set
1111     *  {@link ShortcutInfo#mIntentPersistableExtras}.  Also clear its extras.
1112     * - Clear flags.
1113     *
1114     * TODO Detailed unit tests
1115     */
1116    private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
1117        Preconditions.checkNotNull(shortcut, "Null shortcut detected");
1118        if (shortcut.getActivityComponent() != null) {
1119            Preconditions.checkState(
1120                    shortcut.getPackageName().equals(
1121                            shortcut.getActivityComponent().getPackageName()),
1122                    "Activity package name mismatch");
1123        }
1124
1125        if (!forUpdate) {
1126            shortcut.enforceMandatoryFields();
1127        }
1128        if (shortcut.getIcon() != null) {
1129            ShortcutInfo.validateIcon(shortcut.getIcon());
1130        }
1131
1132        validateForXml(shortcut.getId());
1133        validateForXml(shortcut.getTitle());
1134        validatePersistableBundleForXml(shortcut.getIntentPersistableExtras());
1135        validatePersistableBundleForXml(shortcut.getExtras());
1136
1137        shortcut.replaceFlags(0);
1138    }
1139
1140    // KXmlSerializer is strict and doesn't allow certain characters, so we disallow those
1141    // characters.
1142
1143    private static void validatePersistableBundleForXml(PersistableBundle b) {
1144        if (b == null || b.size() == 0) {
1145            return;
1146        }
1147        for (String key : b.keySet()) {
1148            validateForXml(key);
1149            final Object value = b.get(key);
1150            if (value == null) {
1151                continue;
1152            } else if (value instanceof String) {
1153                validateForXml((String) value);
1154            } else if (value instanceof String[]) {
1155                for (String v : (String[]) value) {
1156                    validateForXml(v);
1157                }
1158            } else if (value instanceof PersistableBundle) {
1159                validatePersistableBundleForXml((PersistableBundle) value);
1160            }
1161        }
1162    }
1163
1164    private static void validateForXml(String s) {
1165        if (TextUtils.isEmpty(s)) {
1166            return;
1167        }
1168        for (int i = s.length() - 1; i >= 0; i--) {
1169            if (!isAllowedInXml(s.charAt(i))) {
1170                throw new IllegalArgumentException("Unsupported character detected in: " + s);
1171            }
1172        }
1173    }
1174
1175    private static boolean isAllowedInXml(char c) {
1176        return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
1177    }
1178
1179    // === APIs ===
1180
1181    @Override
1182    public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1183            @UserIdInt int userId) {
1184        verifyCaller(packageName, userId);
1185
1186        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1187        final int size = newShortcuts.size();
1188
1189        synchronized (mLock) {
1190            getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
1191
1192            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1193
1194            // Throttling.
1195            if (!ps.tryApiCall(this)) {
1196                return false;
1197            }
1198            enforceMaxDynamicShortcuts(size);
1199
1200            // Validate the shortcuts.
1201            for (int i = 0; i < size; i++) {
1202                fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
1203            }
1204
1205            // First, remove all un-pinned; dynamic shortcuts
1206            ps.deleteAllDynamicShortcuts(this);
1207
1208            // Then, add/update all.  We need to make sure to take over "pinned" flag.
1209            for (int i = 0; i < size; i++) {
1210                final ShortcutInfo newShortcut = newShortcuts.get(i);
1211                ps.addDynamicShortcut(this, newShortcut);
1212            }
1213        }
1214        userPackageChanged(packageName, userId);
1215        return true;
1216    }
1217
1218    @Override
1219    public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1220            @UserIdInt int userId) {
1221        verifyCaller(packageName, userId);
1222
1223        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1224        final int size = newShortcuts.size();
1225
1226        synchronized (mLock) {
1227            getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
1228
1229            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1230
1231            // Throttling.
1232            if (!ps.tryApiCall(this)) {
1233                return false;
1234            }
1235
1236            for (int i = 0; i < size; i++) {
1237                final ShortcutInfo source = newShortcuts.get(i);
1238                fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
1239
1240                final ShortcutInfo target = ps.findShortcutById(source.getId());
1241                if (target != null) {
1242                    final boolean replacingIcon = (source.getIcon() != null);
1243                    if (replacingIcon) {
1244                        removeIcon(userId, target);
1245                    }
1246
1247                    target.copyNonNullFieldsFrom(source);
1248
1249                    if (replacingIcon) {
1250                        saveIconAndFixUpShortcut(userId, target);
1251                    }
1252                }
1253            }
1254        }
1255        userPackageChanged(packageName, userId);
1256
1257        return true;
1258    }
1259
1260    @Override
1261    public boolean addDynamicShortcut(String packageName, ShortcutInfo newShortcut,
1262            @UserIdInt int userId) {
1263        verifyCaller(packageName, userId);
1264
1265        synchronized (mLock) {
1266            getUserShortcutsLocked(userId).ensurePackageInfo(this, packageName, userId);
1267
1268            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1269
1270            // Throttling.
1271            if (!ps.tryApiCall(this)) {
1272                return false;
1273            }
1274
1275            // Validate the shortcut.
1276            fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
1277
1278            // Add it.
1279            ps.addDynamicShortcut(this, newShortcut);
1280        }
1281        userPackageChanged(packageName, userId);
1282
1283        return true;
1284    }
1285
1286    @Override
1287    public void deleteDynamicShortcut(String packageName, String shortcutId,
1288            @UserIdInt int userId) {
1289        verifyCaller(packageName, userId);
1290        Preconditions.checkStringNotEmpty(shortcutId, "shortcutId must be provided");
1291
1292        synchronized (mLock) {
1293            getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this, shortcutId);
1294        }
1295        userPackageChanged(packageName, userId);
1296    }
1297
1298    @Override
1299    public void deleteAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
1300        verifyCaller(packageName, userId);
1301
1302        synchronized (mLock) {
1303            getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
1304        }
1305        userPackageChanged(packageName, userId);
1306    }
1307
1308    @Override
1309    public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
1310            @UserIdInt int userId) {
1311        verifyCaller(packageName, userId);
1312        synchronized (mLock) {
1313            return getShortcutsWithQueryLocked(
1314                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1315                    ShortcutInfo::isDynamic);
1316        }
1317    }
1318
1319    @Override
1320    public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
1321            @UserIdInt int userId) {
1322        verifyCaller(packageName, userId);
1323        synchronized (mLock) {
1324            return getShortcutsWithQueryLocked(
1325                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1326                    ShortcutInfo::isPinned);
1327        }
1328    }
1329
1330    private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
1331            @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
1332
1333        final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1334
1335        getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags,
1336                /* callingLauncher= */ null);
1337
1338        return new ParceledListSlice<>(ret);
1339    }
1340
1341    @Override
1342    public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId)
1343            throws RemoteException {
1344        verifyCaller(packageName, userId);
1345
1346        return mMaxDynamicShortcuts;
1347    }
1348
1349    @Override
1350    public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
1351        verifyCaller(packageName, userId);
1352
1353        synchronized (mLock) {
1354            return mMaxDailyUpdates
1355                    - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
1356        }
1357    }
1358
1359    @Override
1360    public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
1361        verifyCaller(packageName, userId);
1362
1363        synchronized (mLock) {
1364            return getNextResetTimeLocked();
1365        }
1366    }
1367
1368    @Override
1369    public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
1370        synchronized (mLock) {
1371            return mMaxIconDimension;
1372        }
1373    }
1374
1375    /**
1376     * Reset all throttling, for developer options and command line.  Only system/shell can call it.
1377     */
1378    @Override
1379    public void resetThrottling() {
1380        enforceSystemOrShell();
1381
1382        resetThrottlingInner(getCallingUserId());
1383    }
1384
1385    void resetThrottlingInner(@UserIdInt int userId) {
1386        synchronized (mLock) {
1387            getUserShortcutsLocked(userId).resetThrottling();
1388        }
1389        scheduleSaveUser(userId);
1390        Slog.i(TAG, "ShortcutManager: throttling counter reset");
1391    }
1392
1393    // We override this method in unit tests to do a simpler check.
1394    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
1395        return hasShortcutHostPermissionInner(callingPackage, userId);
1396    }
1397
1398    // This method is extracted so we can directly call this method from unit tests,
1399    // even when hasShortcutPermission() is overridden.
1400    @VisibleForTesting
1401    boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
1402        synchronized (mLock) {
1403            long start = System.currentTimeMillis();
1404
1405            final ShortcutUser user = getUserShortcutsLocked(userId);
1406
1407            final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
1408
1409            // Default launcher from package manager.
1410            final ComponentName defaultLauncher = injectPackageManagerInternal()
1411                    .getHomeActivitiesAsUser(allHomeCandidates, userId);
1412
1413            ComponentName detected;
1414            if (defaultLauncher != null) {
1415                detected = defaultLauncher;
1416                if (DEBUG) {
1417                    Slog.v(TAG, "Default launcher from PM: " + detected);
1418                }
1419            } else {
1420                detected = user.getLauncherComponent();
1421
1422                // TODO: Make sure it's still enabled.
1423                if (DEBUG) {
1424                    Slog.v(TAG, "Cached launcher: " + detected);
1425                }
1426            }
1427
1428            if (detected == null) {
1429                // If we reach here, that means it's the first check since the user was created,
1430                // and there's already multiple launchers and there's no default set.
1431                // Find the system one with the highest priority.
1432                // (We need to check the priority too because of FallbackHome in Settings.)
1433                // If there's no system launcher yet, then no one can access shortcuts, until
1434                // the user explicitly
1435                final int size = allHomeCandidates.size();
1436
1437                int lastPriority = Integer.MIN_VALUE;
1438                for (int i = 0; i < size; i++) {
1439                    final ResolveInfo ri = allHomeCandidates.get(i);
1440                    if (!ri.activityInfo.applicationInfo.isSystemApp()) {
1441                        continue;
1442                    }
1443                    if (DEBUG) {
1444                        Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
1445                                ri.activityInfo.getComponentName(), ri.priority));
1446                    }
1447                    if (ri.priority < lastPriority) {
1448                        continue;
1449                    }
1450                    detected = ri.activityInfo.getComponentName();
1451                    lastPriority = ri.priority;
1452                }
1453            }
1454            final long end = System.currentTimeMillis();
1455            if (DEBUG) {
1456                Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start));
1457            }
1458            if (detected != null) {
1459                if (DEBUG) {
1460                    Slog.v(TAG, "Detected launcher: " + detected);
1461                }
1462                user.setLauncherComponent(this, detected);
1463                return detected.getPackageName().equals(callingPackage);
1464            } else {
1465                // Default launcher not found.
1466                return false;
1467            }
1468        }
1469    }
1470
1471    // === House keeping ===
1472
1473    @VisibleForTesting
1474    void cleanUpPackageLocked(String packageName, int userId) {
1475        final boolean wasUserLoaded = isUserLoadedLocked(userId);
1476
1477        final ShortcutUser mUser = getUserShortcutsLocked(userId);
1478        boolean doNotify = false;
1479
1480        // First, remove the package from the package list (if the package is a publisher).
1481        if (mUser.getPackages().remove(packageName) != null) {
1482            doNotify = true;
1483        }
1484        // Also remove from the launcher list (if the package is a launcher).
1485        mUser.getLaunchers().remove(packageName);
1486
1487        // Then remove pinned shortcuts from all launchers.
1488        for (int i = mUser.getLaunchers().size() - 1; i >= 0; i--) {
1489            mUser.getLaunchers().valueAt(i).cleanUpPackage(packageName);
1490        }
1491        // Now there may be orphan shortcuts because we removed pinned shortucts at the previous
1492        // step.  Remove them too.
1493        for (int i = mUser.getPackages().size() - 1; i >= 0; i--) {
1494            mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
1495        }
1496
1497        // Remove the package info too.
1498        mUser.getPackageInfos().remove(packageName);
1499
1500        scheduleSaveUser(userId);
1501
1502        if (doNotify) {
1503            notifyListeners(packageName, userId);
1504        }
1505
1506        if (!wasUserLoaded) {
1507            // Note this will execute the scheduled save.
1508            unloadUserLocked(userId);
1509        }
1510    }
1511
1512    /**
1513     * Entry point from {@link LauncherApps}.
1514     */
1515    private class LocalService extends ShortcutServiceInternal {
1516        @Override
1517        public List<ShortcutInfo> getShortcuts(
1518                @NonNull String callingPackage, long changedSince,
1519                @Nullable String packageName, @Nullable ComponentName componentName,
1520                int queryFlags, int userId) {
1521            final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1522            final int cloneFlag =
1523                    ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0)
1524                            ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER
1525                            : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
1526
1527            synchronized (mLock) {
1528                if (packageName != null) {
1529                    getShortcutsInnerLocked(
1530                            callingPackage, packageName, changedSince,
1531                            componentName, queryFlags, userId, ret, cloneFlag);
1532                } else {
1533                    final ArrayMap<String, ShortcutPackage> packages =
1534                            getUserShortcutsLocked(userId).getPackages();
1535                    for (int i = packages.size() - 1; i >= 0; i--) {
1536                        getShortcutsInnerLocked(
1537                                callingPackage, packages.keyAt(i), changedSince,
1538                                componentName, queryFlags, userId, ret, cloneFlag);
1539                    }
1540                }
1541            }
1542            return ret;
1543        }
1544
1545        private void getShortcutsInnerLocked(@NonNull String callingPackage,
1546                @Nullable String packageName,long changedSince,
1547                @Nullable ComponentName componentName, int queryFlags,
1548                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
1549            getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
1550                    (ShortcutInfo si) -> {
1551                        if (si.getLastChangedTimestamp() < changedSince) {
1552                            return false;
1553                        }
1554                        if (componentName != null
1555                                && !componentName.equals(si.getActivityComponent())) {
1556                            return false;
1557                        }
1558                        final boolean matchDynamic =
1559                                ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
1560                                        && si.isDynamic();
1561                        final boolean matchPinned =
1562                                ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
1563                                        && si.isPinned();
1564                        return matchDynamic || matchPinned;
1565                    }, cloneFlag, callingPackage);
1566        }
1567
1568        @Override
1569        public List<ShortcutInfo> getShortcutInfo(
1570                @NonNull String callingPackage,
1571                @NonNull String packageName, @Nullable List<String> ids, int userId) {
1572            // Calling permission must be checked by LauncherAppsImpl.
1573            Preconditions.checkStringNotEmpty(packageName, "packageName");
1574
1575            final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
1576            final ArraySet<String> idSet = new ArraySet<>(ids);
1577            synchronized (mLock) {
1578                getPackageShortcutsLocked(packageName, userId).findAll(
1579                        ShortcutService.this, ret,
1580                        (ShortcutInfo si) -> idSet.contains(si.getId()),
1581                        ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER, callingPackage);
1582            }
1583            return ret;
1584        }
1585
1586        @Override
1587        public void pinShortcuts(@NonNull String callingPackage, @NonNull String packageName,
1588                @NonNull List<String> shortcutIds, int userId) {
1589            // Calling permission must be checked by LauncherAppsImpl.
1590            Preconditions.checkStringNotEmpty(packageName, "packageName");
1591            Preconditions.checkNotNull(shortcutIds, "shortcutIds");
1592
1593            synchronized (mLock) {
1594                getUserShortcutsLocked(userId).ensurePackageInfo(
1595                        ShortcutService.this, callingPackage, userId);
1596
1597                getLauncherShortcuts(callingPackage, userId).pinShortcuts(
1598                        ShortcutService.this, packageName, shortcutIds);
1599            }
1600            userPackageChanged(packageName, userId);
1601        }
1602
1603        @Override
1604        public Intent createShortcutIntent(@NonNull String callingPackage,
1605                @NonNull String packageName, @NonNull String shortcutId, int userId) {
1606            // Calling permission must be checked by LauncherAppsImpl.
1607            Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
1608            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
1609
1610            synchronized (mLock) {
1611                final ShortcutInfo fullShortcut =
1612                        getPackageShortcutsLocked(packageName, userId)
1613                        .findShortcutById(shortcutId);
1614                return fullShortcut == null ? null : fullShortcut.getIntent();
1615            }
1616        }
1617
1618        @Override
1619        public void addListener(@NonNull ShortcutChangeListener listener) {
1620            synchronized (mLock) {
1621                mListeners.add(Preconditions.checkNotNull(listener));
1622            }
1623        }
1624
1625        @Override
1626        public int getShortcutIconResId(@NonNull String callingPackage,
1627                @NonNull ShortcutInfo shortcut, int userId) {
1628            Preconditions.checkNotNull(shortcut, "shortcut");
1629
1630            synchronized (mLock) {
1631                final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1632                        shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
1633                return (shortcutInfo != null && shortcutInfo.hasIconResource())
1634                        ? shortcutInfo.getIconResourceId() : 0;
1635            }
1636        }
1637
1638        @Override
1639        public ParcelFileDescriptor getShortcutIconFd(@NonNull String callingPackage,
1640                @NonNull ShortcutInfo shortcutIn, int userId) {
1641            Preconditions.checkNotNull(shortcutIn, "shortcut");
1642
1643            synchronized (mLock) {
1644                final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1645                        shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
1646                if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
1647                    return null;
1648                }
1649                try {
1650                    if (shortcutInfo.getBitmapPath() == null) {
1651                        Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
1652                        return null;
1653                    }
1654                    return ParcelFileDescriptor.open(
1655                            new File(shortcutInfo.getBitmapPath()),
1656                            ParcelFileDescriptor.MODE_READ_ONLY);
1657                } catch (FileNotFoundException e) {
1658                    Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath());
1659                    return null;
1660                }
1661            }
1662        }
1663
1664        @Override
1665        public boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
1666            return ShortcutService.this.hasShortcutHostPermission(callingPackage, userId);
1667        }
1668    }
1669
1670    /**
1671     * Package event callbacks.
1672     */
1673    @VisibleForTesting
1674    final PackageMonitor mPackageMonitor = new PackageMonitor() {
1675        @Override
1676        public void onPackageAdded(String packageName, int uid) {
1677            handlePackageAdded(packageName, getChangingUserId());
1678        }
1679
1680        @Override
1681        public void onPackageUpdateFinished(String packageName, int uid) {
1682            handlePackageUpdateFinished(packageName, getChangingUserId());
1683        }
1684
1685        @Override
1686        public void onPackageRemoved(String packageName, int uid) {
1687            handlePackageRemoved(packageName, getChangingUserId());
1688        }
1689    };
1690
1691    /**
1692     * Called when a user is unlocked.  Check all known packages still exist, and otherwise
1693     * perform cleanup.
1694     */
1695    private void cleanupGonePackages(@UserIdInt int userId) {
1696        if (DEBUG) {
1697            Slog.d(TAG, "cleanupGonePackages() userId=" + userId);
1698        }
1699        ArrayList<String> gonePackages = null;
1700
1701        final ShortcutUser user = getUserShortcutsLocked(userId);
1702        final ArrayMap<String, ShortcutPackageInfo> infos = user.getPackageInfos();
1703        for (int i = infos.size() -1; i >= 0; i--) {
1704            final ShortcutPackageInfo info = infos.valueAt(i);
1705            if (info.isShadow()) {
1706                continue;
1707            }
1708            if (isPackageInstalled(info.getPackageName(), userId)) {
1709                continue;
1710            }
1711            gonePackages = ArrayUtils.add(gonePackages, info.getPackageName());
1712        }
1713        if (gonePackages != null) {
1714            for (int i = gonePackages.size() - 1; i >= 0; i--) {
1715                handlePackageGone(gonePackages.get(i), userId);
1716            }
1717        }
1718    }
1719
1720    private void handlePackageAdded(String packageName, @UserIdInt int userId) {
1721        if (DEBUG) {
1722            Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
1723        }
1724        synchronized (mLock) {
1725            final ArrayMap<String, ShortcutPackageInfo> infos =
1726                    getUserShortcutsLocked(userId).getPackageInfos();
1727            final ShortcutPackageInfo existing = infos.get(packageName);
1728
1729            if (existing != null && existing.isShadow()) {
1730                Slog.w(TAG, "handlePackageAdded: TODO Restore not implemented");
1731            }
1732        }
1733    }
1734
1735    private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
1736        final PackageInfo pi = getPackageInfo(packageName, userId);
1737        if (pi == null) {
1738            Slog.w(TAG, "Package not found: " + packageName);
1739            return;
1740        }
1741        synchronized (mLock) {
1742            final ShortcutPackageInfo spi =
1743                    getUserShortcutsLocked(userId).getPackageInfos().get(packageName);
1744            if (spi != null) {
1745                spi.refreshAndSave(this, userId);
1746            }
1747        }
1748    }
1749
1750    private void handlePackageRemoved(String packageName, @UserIdInt int userId) {
1751        if (DEBUG) {
1752            Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId));
1753        }
1754        synchronized (mLock) {
1755            cleanUpPackageLocked(packageName, userId);
1756        }
1757    }
1758
1759    private void handlePackageGone(String packageName, @UserIdInt int userId) {
1760        if (DEBUG) {
1761            Slog.d(TAG, String.format("handlePackageGone: %s user=%d", packageName, userId));
1762        }
1763        synchronized (mLock) {
1764            cleanUpPackageLocked(packageName, userId);
1765        }
1766    }
1767
1768    // === Backup & restore ===
1769
1770    PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
1771        return getPackageInfo(packageName, userId, /*getSignatures=*/ false);
1772    }
1773
1774    PackageInfo getPackageInfo(String packageName, @UserIdInt int userId,
1775            boolean getSignatures) {
1776        return injectPackageInfo(packageName, userId, getSignatures);
1777    }
1778
1779    @VisibleForTesting
1780    PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
1781            boolean getSignatures) {
1782        try {
1783            return mIPackageManager.getPackageInfo(packageName,
1784                    PackageManager.MATCH_DIRECT_BOOT_AWARE
1785                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
1786                    | (getSignatures ? PackageManager.GET_SIGNATURES : 0)
1787                    , userId);
1788        } catch (RemoteException e) {
1789            // Shouldn't happen.
1790            Slog.wtf(TAG, "RemoteException", e);
1791            return null;
1792        }
1793    }
1794
1795    boolean shouldBackupApp(String packageName, int userId) {
1796        final PackageInfo pi = getPackageInfo(packageName, userId);
1797        return (pi != null) &&
1798                ((pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0);
1799    }
1800
1801    private boolean isPackageInstalled(String packageName, int userId) {
1802        return getPackageInfo(packageName, userId) != null;
1803    }
1804
1805    // === Dump ===
1806
1807    @Override
1808    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1809        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1810                != PackageManager.PERMISSION_GRANTED) {
1811            pw.println("Permission Denial: can't dump UserManager from from pid="
1812                    + Binder.getCallingPid()
1813                    + ", uid=" + Binder.getCallingUid()
1814                    + " without permission "
1815                    + android.Manifest.permission.DUMP);
1816            return;
1817        }
1818        dumpInner(pw);
1819    }
1820
1821    @VisibleForTesting
1822    void dumpInner(PrintWriter pw) {
1823        synchronized (mLock) {
1824            final long now = injectCurrentTimeMillis();
1825            pw.print("Now: [");
1826            pw.print(now);
1827            pw.print("] ");
1828            pw.print(formatTime(now));
1829
1830            pw.print("  Raw last reset: [");
1831            pw.print(mRawLastResetTime);
1832            pw.print("] ");
1833            pw.print(formatTime(mRawLastResetTime));
1834
1835            final long last = getLastResetTimeLocked();
1836            pw.print("  Last reset: [");
1837            pw.print(last);
1838            pw.print("] ");
1839            pw.print(formatTime(last));
1840
1841            final long next = getNextResetTimeLocked();
1842            pw.print("  Next reset: [");
1843            pw.print(next);
1844            pw.print("] ");
1845            pw.print(formatTime(next));
1846            pw.println();
1847
1848            pw.print("  Max icon dim: ");
1849            pw.print(mMaxIconDimension);
1850            pw.print("  Icon format: ");
1851            pw.print(mIconPersistFormat);
1852            pw.print("  Icon quality: ");
1853            pw.print(mIconPersistQuality);
1854            pw.println();
1855
1856
1857            for (int i = 0; i < mUsers.size(); i++) {
1858                pw.println();
1859                mUsers.valueAt(i).dump(this, pw, "  ");
1860            }
1861        }
1862    }
1863
1864    static String formatTime(long time) {
1865        Time tobj = new Time();
1866        tobj.set(time);
1867        return tobj.format("%Y-%m-%d %H:%M:%S");
1868    }
1869
1870    // === Shell support ===
1871
1872    @Override
1873    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1874            String[] args, ResultReceiver resultReceiver) throws RemoteException {
1875
1876        enforceShell();
1877
1878        (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
1879    }
1880
1881    static class CommandException extends Exception {
1882        public CommandException(String message) {
1883            super(message);
1884        }
1885    }
1886
1887    /**
1888     * Handle "adb shell cmd".
1889     */
1890    private class MyShellCommand extends ShellCommand {
1891
1892        private int mUserId = UserHandle.USER_SYSTEM;
1893
1894        private void parseOptions(boolean takeUser)
1895                throws CommandException {
1896            String opt;
1897            while ((opt = getNextOption()) != null) {
1898                switch (opt) {
1899                    case "--user":
1900                        if (takeUser) {
1901                            mUserId = UserHandle.parseUserArg(getNextArgRequired());
1902                            break;
1903                        }
1904                        // fallthrough
1905                    default:
1906                        throw new CommandException("Unknown option: " + opt);
1907                }
1908            }
1909        }
1910
1911        @Override
1912        public int onCommand(String cmd) {
1913            if (cmd == null) {
1914                return handleDefaultCommands(cmd);
1915            }
1916            final PrintWriter pw = getOutPrintWriter();
1917            try {
1918                switch (cmd) {
1919                    case "reset-package-throttling":
1920                        handleResetPackageThrottling();
1921                        break;
1922                    case "reset-throttling":
1923                        handleResetThrottling();
1924                        break;
1925                    case "override-config":
1926                        handleOverrideConfig();
1927                        break;
1928                    case "reset-config":
1929                        handleResetConfig();
1930                        break;
1931                    case "clear-default-launcher":
1932                        handleClearDefaultLauncher();
1933                        break;
1934                    case "get-default-launcher":
1935                        handleGetDefaultLauncher();
1936                        break;
1937                    case "refresh-default-launcher":
1938                        handleRefreshDefaultLauncher();
1939                        break;
1940                    default:
1941                        return handleDefaultCommands(cmd);
1942                }
1943            } catch (CommandException e) {
1944                pw.println("Error: " + e.getMessage());
1945                return 1;
1946            }
1947            pw.println("Success");
1948            return 0;
1949        }
1950
1951        @Override
1952        public void onHelp() {
1953            final PrintWriter pw = getOutPrintWriter();
1954            pw.println("Usage: cmd shortcut COMMAND [options ...]");
1955            pw.println();
1956            pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
1957            pw.println("    Reset throttling for a package");
1958            pw.println();
1959            pw.println("cmd shortcut reset-throttling");
1960            pw.println("    Reset throttling for all packages and users");
1961            pw.println();
1962            pw.println("cmd shortcut override-config CONFIG");
1963            pw.println("    Override the configuration for testing (will last until reboot)");
1964            pw.println();
1965            pw.println("cmd shortcut reset-config");
1966            pw.println("    Reset the configuration set with \"update-config\"");
1967            pw.println();
1968            pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
1969            pw.println("    Clear the cached default launcher");
1970            pw.println();
1971            pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
1972            pw.println("    Show the cached default launcher");
1973            pw.println();
1974            pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
1975            pw.println("    Refresh the cached default launcher");
1976            pw.println();
1977        }
1978
1979        private int handleResetThrottling() throws CommandException {
1980            parseOptions(/* takeUser =*/ true);
1981
1982            resetThrottlingInner(mUserId);
1983            return 0;
1984        }
1985
1986        private void handleResetPackageThrottling() throws CommandException {
1987            parseOptions(/* takeUser =*/ true);
1988
1989            final String packageName = getNextArgRequired();
1990
1991            synchronized (mLock) {
1992                getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine();
1993                saveUserLocked(mUserId);
1994            }
1995        }
1996
1997        private void handleOverrideConfig() throws CommandException {
1998            final String config = getNextArgRequired();
1999
2000            synchronized (mLock) {
2001                if (!updateConfigurationLocked(config)) {
2002                    throw new CommandException("override-config failed.  See logcat for details.");
2003                }
2004            }
2005        }
2006
2007        private void handleResetConfig() {
2008            synchronized (mLock) {
2009                loadConfigurationLocked();
2010            }
2011        }
2012
2013        private void clearLauncher() {
2014            synchronized (mLock) {
2015                getUserShortcutsLocked(mUserId).setLauncherComponent(
2016                        ShortcutService.this, null);
2017            }
2018        }
2019
2020        private void showLauncher() {
2021            synchronized (mLock) {
2022                // This ensures to set the cached launcher.  Package name doesn't matter.
2023                hasShortcutHostPermissionInner("-", mUserId);
2024
2025                getOutPrintWriter().println("Launcher: "
2026                        + getUserShortcutsLocked(mUserId).getLauncherComponent());
2027            }
2028        }
2029
2030        private void handleClearDefaultLauncher() throws CommandException {
2031            parseOptions(/* takeUser =*/ true);
2032
2033            clearLauncher();
2034        }
2035
2036        private void handleGetDefaultLauncher() throws CommandException {
2037            parseOptions(/* takeUser =*/ true);
2038
2039            showLauncher();
2040        }
2041
2042        private void handleRefreshDefaultLauncher() throws CommandException {
2043            parseOptions(/* takeUser =*/ true);
2044
2045            clearLauncher();
2046            showLauncher();
2047        }
2048    }
2049
2050    // === Unit test support ===
2051
2052    // Injection point.
2053    @VisibleForTesting
2054    long injectCurrentTimeMillis() {
2055        return System.currentTimeMillis();
2056    }
2057
2058    // Injection point.
2059    @VisibleForTesting
2060    int injectBinderCallingUid() {
2061        return getCallingUid();
2062    }
2063
2064    private int getCallingUserId() {
2065        return UserHandle.getUserId(injectBinderCallingUid());
2066    }
2067
2068    // Injection point.
2069    @VisibleForTesting
2070    long injectClearCallingIdentity() {
2071        return Binder.clearCallingIdentity();
2072    }
2073
2074    // Injection point.
2075    @VisibleForTesting
2076    void injectRestoreCallingIdentity(long token) {
2077        Binder.restoreCallingIdentity(token);
2078    }
2079
2080    final void wtf(String message) {
2081        Slog.wtf(TAG, message, /* exception= */ null);
2082    }
2083
2084    void wtf(String message, Exception e) {
2085        Slog.wtf(TAG, message, e);
2086    }
2087
2088    @VisibleForTesting
2089    File injectSystemDataPath() {
2090        return Environment.getDataSystemDirectory();
2091    }
2092
2093    @VisibleForTesting
2094    File injectUserDataPath(@UserIdInt int userId) {
2095        return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
2096    }
2097
2098    @VisibleForTesting
2099    boolean injectIsLowRamDevice() {
2100        return ActivityManager.isLowRamDeviceStatic();
2101    }
2102
2103    @VisibleForTesting
2104    PackageManagerInternal injectPackageManagerInternal() {
2105        return mPackageManagerInternal;
2106    }
2107
2108    @VisibleForTesting
2109    File getUserBitmapFilePath(@UserIdInt int userId) {
2110        return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
2111    }
2112
2113    @VisibleForTesting
2114    SparseArray<ShortcutUser> getShortcutsForTest() {
2115        return mUsers;
2116    }
2117
2118    @VisibleForTesting
2119    int getMaxDynamicShortcutsForTest() {
2120        return mMaxDynamicShortcuts;
2121    }
2122
2123    @VisibleForTesting
2124    int getMaxDailyUpdatesForTest() {
2125        return mMaxDailyUpdates;
2126    }
2127
2128    @VisibleForTesting
2129    long getResetIntervalForTest() {
2130        return mResetInterval;
2131    }
2132
2133    @VisibleForTesting
2134    int getMaxIconDimensionForTest() {
2135        return mMaxIconDimension;
2136    }
2137
2138    @VisibleForTesting
2139    CompressFormat getIconPersistFormatForTest() {
2140        return mIconPersistFormat;
2141    }
2142
2143    @VisibleForTesting
2144    int getIconPersistQualityForTest() {
2145        return mIconPersistQuality;
2146    }
2147
2148    @VisibleForTesting
2149    ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
2150        synchronized (mLock) {
2151            final ShortcutUser user = mUsers.get(userId);
2152            if (user == null) return null;
2153
2154            final ShortcutPackage pkg = user.getPackages().get(packageName);
2155            if (pkg == null) return null;
2156
2157            return pkg.findShortcutById(shortcutId);
2158        }
2159    }
2160
2161    @VisibleForTesting
2162    ShortcutPackageInfo getPackageInfoForTest(String packageName, int userId) {
2163        synchronized (mLock) {
2164            final ShortcutUser user = mUsers.get(userId);
2165            if (user == null) return null;
2166
2167            return user.getPackageInfos().get(packageName);
2168        }
2169    }
2170}
2171