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