ShortcutService.java revision 4703746f500933b920236a72f70f629304ea2578
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.IntDef;
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.UserIdInt;
22import android.app.ActivityManager;
23import android.app.ActivityManagerInternal;
24import android.app.AppGlobals;
25import android.app.IUidObserver;
26import android.app.usage.UsageStatsManagerInternal;
27import android.appwidget.AppWidgetProviderInfo;
28import android.content.BroadcastReceiver;
29import android.content.ComponentName;
30import android.content.Context;
31import android.content.Intent;
32import android.content.IntentFilter;
33import android.content.IntentSender;
34import android.content.IntentSender.SendIntentException;
35import android.content.pm.ActivityInfo;
36import android.content.pm.ApplicationInfo;
37import android.content.pm.IPackageManager;
38import android.content.pm.IShortcutService;
39import android.content.pm.LauncherApps;
40import android.content.pm.LauncherApps.ShortcutQuery;
41import android.content.pm.PackageInfo;
42import android.content.pm.PackageManager;
43import android.content.pm.PackageManager.NameNotFoundException;
44import android.content.pm.PackageManagerInternal;
45import android.content.pm.ParceledListSlice;
46import android.content.pm.ResolveInfo;
47import android.content.pm.ShortcutInfo;
48import android.content.pm.ShortcutServiceInternal;
49import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
50import android.content.pm.UserInfo;
51import android.content.res.Resources;
52import android.content.res.XmlResourceParser;
53import android.graphics.Bitmap;
54import android.graphics.Bitmap.CompressFormat;
55import android.graphics.Canvas;
56import android.graphics.RectF;
57import android.graphics.drawable.AdaptiveIconDrawable;
58import android.graphics.drawable.Icon;
59import android.net.Uri;
60import android.os.Binder;
61import android.os.Build;
62import android.os.Bundle;
63import android.os.Environment;
64import android.os.FileUtils;
65import android.os.Handler;
66import android.os.LocaleList;
67import android.os.Looper;
68import android.os.ParcelFileDescriptor;
69import android.os.PersistableBundle;
70import android.os.Process;
71import android.os.RemoteException;
72import android.os.ResultReceiver;
73import android.os.SELinux;
74import android.os.ServiceManager;
75import android.os.ShellCallback;
76import android.os.ShellCommand;
77import android.os.SystemClock;
78import android.os.UserHandle;
79import android.os.UserManager;
80import android.text.TextUtils;
81import android.text.format.Time;
82import android.util.ArraySet;
83import android.util.AtomicFile;
84import android.util.KeyValueListParser;
85import android.util.Log;
86import android.util.Slog;
87import android.util.SparseArray;
88import android.util.SparseBooleanArray;
89import android.util.SparseIntArray;
90import android.util.SparseLongArray;
91import android.util.TypedValue;
92import android.util.Xml;
93import android.view.IWindowManager;
94
95import com.android.internal.annotations.GuardedBy;
96import com.android.internal.annotations.VisibleForTesting;
97import com.android.internal.os.BackgroundThread;
98import com.android.internal.util.DumpUtils;
99import com.android.internal.util.FastXmlSerializer;
100import com.android.internal.util.Preconditions;
101import com.android.server.LocalServices;
102import com.android.server.SystemService;
103import com.android.server.pm.ShortcutUser.PackageWithUser;
104
105import libcore.io.IoUtils;
106
107import org.json.JSONArray;
108import org.json.JSONException;
109import org.json.JSONObject;
110import org.xmlpull.v1.XmlPullParser;
111import org.xmlpull.v1.XmlPullParserException;
112import org.xmlpull.v1.XmlSerializer;
113
114import java.io.BufferedInputStream;
115import java.io.BufferedOutputStream;
116import java.io.ByteArrayInputStream;
117import java.io.ByteArrayOutputStream;
118import java.io.File;
119import java.io.FileDescriptor;
120import java.io.FileInputStream;
121import java.io.FileNotFoundException;
122import java.io.FileOutputStream;
123import java.io.IOException;
124import java.io.InputStream;
125import java.io.OutputStream;
126import java.io.PrintWriter;
127import java.lang.annotation.Retention;
128import java.lang.annotation.RetentionPolicy;
129import java.net.URISyntaxException;
130import java.nio.charset.StandardCharsets;
131import java.util.ArrayList;
132import java.util.Collections;
133import java.util.List;
134import java.util.concurrent.atomic.AtomicBoolean;
135import java.util.function.Consumer;
136import java.util.function.Predicate;
137
138/**
139 * TODO:
140 * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
141 *   -> But TypedValue.applyDimension() doesn't differentiate x and y..?
142 *
143 * - Detect when already registered instances are passed to APIs again, which might break
144 * internal bitmap handling.
145 */
146public class ShortcutService extends IShortcutService.Stub {
147    static final String TAG = "ShortcutService";
148
149    static final boolean DEBUG = false; // STOPSHIP if true
150    static final boolean DEBUG_LOAD = false; // STOPSHIP if true
151    static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
152
153    @VisibleForTesting
154    static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
155
156    @VisibleForTesting
157    static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
158
159    @VisibleForTesting
160    static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
161
162    @VisibleForTesting
163    static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
164
165    @VisibleForTesting
166    static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
167
168    @VisibleForTesting
169    static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
170
171    @VisibleForTesting
172    static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
173
174    @VisibleForTesting
175    static final int DEFAULT_SAVE_DELAY_MS = 3000;
176
177    @VisibleForTesting
178    static final String FILENAME_BASE_STATE = "shortcut_service.xml";
179
180    @VisibleForTesting
181    static final String DIRECTORY_PER_USER = "shortcut_service";
182
183    @VisibleForTesting
184    static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
185
186    static final String DIRECTORY_BITMAPS = "bitmaps";
187
188    private static final String TAG_ROOT = "root";
189    private static final String TAG_LAST_RESET_TIME = "last_reset_time";
190
191    private static final String ATTR_VALUE = "value";
192
193    private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER;
194
195    private static final String KEY_SHORTCUT = "shortcut";
196    private static final String KEY_LOW_RAM = "lowRam";
197    private static final String KEY_ICON_SIZE = "iconSize";
198
199    private static final String DUMMY_MAIN_ACTIVITY = "android.__dummy__";
200
201    @VisibleForTesting
202    interface ConfigConstants {
203        /**
204         * Key name for the save delay, in milliseconds. (int)
205         */
206        String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
207
208        /**
209         * Key name for the throttling reset interval, in seconds. (long)
210         */
211        String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
212
213        /**
214         * Key name for the max number of modifying API calls per app for every interval. (int)
215         */
216        String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval";
217
218        /**
219         * Key name for the max icon dimensions in DP, for non-low-memory devices.
220         */
221        String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
222
223        /**
224         * Key name for the max icon dimensions in DP, for low-memory devices.
225         */
226        String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
227
228        /**
229         * Key name for the max dynamic shortcuts per activity. (int)
230         */
231        String KEY_MAX_SHORTCUTS = "max_shortcuts";
232
233        /**
234         * Key name for icon compression quality, 0-100.
235         */
236        String KEY_ICON_QUALITY = "icon_quality";
237
238        /**
239         * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
240         */
241        String KEY_ICON_FORMAT = "icon_format";
242    }
243
244    final Context mContext;
245
246    private final Object mLock = new Object();
247
248    private static List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0);
249
250    // Temporarily reverted to anonymous inner class form due to: b/32554459
251    private static Predicate<ResolveInfo> ACTIVITY_NOT_EXPORTED = new Predicate<ResolveInfo>() {
252        public boolean test(ResolveInfo ri) {
253            return !ri.activityInfo.exported;
254        }
255    };
256
257    // Temporarily reverted to anonymous inner class form due to: b/32554459
258    private static Predicate<PackageInfo> PACKAGE_NOT_INSTALLED = new Predicate<PackageInfo>() {
259        public boolean test(PackageInfo pi) {
260            return !isInstalled(pi);
261        }
262    };
263
264    private final Handler mHandler;
265
266    @GuardedBy("mLock")
267    private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
268
269    @GuardedBy("mLock")
270    private long mRawLastResetTime;
271
272    /**
273     * User ID -> UserShortcuts
274     */
275    @GuardedBy("mLock")
276    private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
277
278    /**
279     * Max number of dynamic + manifest shortcuts that each application can have at a time.
280     */
281    private int mMaxShortcuts;
282
283    /**
284     * Max number of updating API calls that each application can make during the interval.
285     */
286    int mMaxUpdatesPerInterval;
287
288    /**
289     * Actual throttling-reset interval.  By default it's a day.
290     */
291    private long mResetInterval;
292
293    /**
294     * Icon max width/height in pixels.
295     */
296    private int mMaxIconDimension;
297
298    private CompressFormat mIconPersistFormat;
299    private int mIconPersistQuality;
300
301    private int mSaveDelayMillis;
302
303    private final IPackageManager mIPackageManager;
304    private final PackageManagerInternal mPackageManagerInternal;
305    private final UserManager mUserManager;
306    private final UsageStatsManagerInternal mUsageStatsManagerInternal;
307    private final ActivityManagerInternal mActivityManagerInternal;
308
309    private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
310    private final ShortcutBitmapSaver mShortcutBitmapSaver;
311
312    @GuardedBy("mLock")
313    final SparseIntArray mUidState = new SparseIntArray();
314
315    @GuardedBy("mLock")
316    final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
317
318    @GuardedBy("mLock")
319    private List<Integer> mDirtyUserIds = new ArrayList<>();
320
321    private final AtomicBoolean mBootCompleted = new AtomicBoolean();
322
323    private static final int PACKAGE_MATCH_FLAGS =
324            PackageManager.MATCH_DIRECT_BOOT_AWARE
325                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
326                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
327
328    @GuardedBy("mLock")
329    final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray();
330
331    // Stats
332    @VisibleForTesting
333    interface Stats {
334        int GET_DEFAULT_HOME = 0;
335        int GET_PACKAGE_INFO = 1;
336        int GET_PACKAGE_INFO_WITH_SIG = 2;
337        int GET_APPLICATION_INFO = 3;
338        int LAUNCHER_PERMISSION_CHECK = 4;
339        int CLEANUP_DANGLING_BITMAPS = 5;
340        int GET_ACTIVITY_WITH_METADATA = 6;
341        int GET_INSTALLED_PACKAGES = 7;
342        int CHECK_PACKAGE_CHANGES = 8;
343        int GET_APPLICATION_RESOURCES = 9;
344        int RESOURCE_NAME_LOOKUP = 10;
345        int GET_LAUNCHER_ACTIVITY = 11;
346        int CHECK_LAUNCHER_ACTIVITY = 12;
347        int IS_ACTIVITY_ENABLED = 13;
348        int PACKAGE_UPDATE_CHECK = 14;
349        int ASYNC_PRELOAD_USER_DELAY = 15;
350        int GET_DEFAULT_LAUNCHER = 16;
351
352        int COUNT = GET_DEFAULT_LAUNCHER + 1;
353    }
354
355    private static final String[] STAT_LABELS = {
356            "getHomeActivities()",
357            "Launcher permission check",
358            "getPackageInfo()",
359            "getPackageInfo(SIG)",
360            "getApplicationInfo",
361            "cleanupDanglingBitmaps",
362            "getActivity+metadata",
363            "getInstalledPackages",
364            "checkPackageChanges",
365            "getApplicationResources",
366            "resourceNameLookup",
367            "getLauncherActivity",
368            "checkLauncherActivity",
369            "isActivityEnabled",
370            "packageUpdateCheck",
371            "asyncPreloadUserDelay",
372            "getDefaultLauncher()"
373    };
374
375    final Object mStatLock = new Object();
376
377    @GuardedBy("mStatLock")
378    private final int[] mCountStats = new int[Stats.COUNT];
379
380    @GuardedBy("mStatLock")
381    private final long[] mDurationStats = new long[Stats.COUNT];
382
383    private static final int PROCESS_STATE_FOREGROUND_THRESHOLD =
384            ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
385
386    static final int OPERATION_SET = 0;
387    static final int OPERATION_ADD = 1;
388    static final int OPERATION_UPDATE = 2;
389
390    /** @hide */
391    @IntDef(value = {
392            OPERATION_SET,
393            OPERATION_ADD,
394            OPERATION_UPDATE
395    })
396    @Retention(RetentionPolicy.SOURCE)
397    @interface ShortcutOperation {
398    }
399
400    @GuardedBy("mLock")
401    private int mWtfCount = 0;
402
403    @GuardedBy("mLock")
404    private Exception mLastWtfStacktrace;
405
406    static class InvalidFileFormatException extends Exception {
407        public InvalidFileFormatException(String message, Throwable cause) {
408            super(message, cause);
409        }
410    }
411
412    public ShortcutService(Context context) {
413        this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
414    }
415
416    @VisibleForTesting
417    ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) {
418        mContext = Preconditions.checkNotNull(context);
419        LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
420        mHandler = new Handler(looper);
421        mIPackageManager = AppGlobals.getPackageManager();
422        mPackageManagerInternal = Preconditions.checkNotNull(
423                LocalServices.getService(PackageManagerInternal.class));
424        mUserManager = Preconditions.checkNotNull(context.getSystemService(UserManager.class));
425        mUsageStatsManagerInternal = Preconditions.checkNotNull(
426                LocalServices.getService(UsageStatsManagerInternal.class));
427        mActivityManagerInternal = Preconditions.checkNotNull(
428                LocalServices.getService(ActivityManagerInternal.class));
429
430        mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
431        mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
432
433        if (onlyForPackageManagerApis) {
434            return; // Don't do anything further.  For unit tests only.
435        }
436
437        // Register receivers.
438
439        // We need to set a priority, so let's just not use PackageMonitor for now.
440        // TODO Refactor PackageMonitor to support priorities.
441        final IntentFilter packageFilter = new IntentFilter();
442        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
443        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
444        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
445        packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
446        packageFilter.addDataScheme("package");
447        packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
448        mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL,
449                packageFilter, null, mHandler);
450
451        final IntentFilter preferedActivityFilter = new IntentFilter();
452        preferedActivityFilter.addAction(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
453        preferedActivityFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
454        mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL,
455                preferedActivityFilter, null, mHandler);
456
457        final IntentFilter localeFilter = new IntentFilter();
458        localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
459        localeFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
460        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL,
461                localeFilter, null, mHandler);
462
463        injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
464                | ActivityManager.UID_OBSERVER_GONE);
465    }
466
467    void logDurationStat(int statId, long start) {
468        synchronized (mStatLock) {
469            mCountStats[statId]++;
470            mDurationStats[statId] += (injectElapsedRealtime() - start);
471        }
472    }
473
474    public String injectGetLocaleTagsForUser(@UserIdInt int userId) {
475        // TODO This should get the per-user locale.  b/30123329 b/30119489
476        return LocaleList.getDefault().toLanguageTags();
477    }
478
479    final private IUidObserver mUidObserver = new IUidObserver.Stub() {
480        @Override
481        public void onUidStateChanged(int uid, int procState, long procStateSeq)
482                throws RemoteException {
483            handleOnUidStateChanged(uid, procState);
484        }
485
486        @Override
487        public void onUidGone(int uid, boolean disabled) throws RemoteException {
488            handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
489        }
490
491        @Override
492        public void onUidActive(int uid) throws RemoteException {
493        }
494
495        @Override
496        public void onUidIdle(int uid, boolean disabled) throws RemoteException {
497        }
498    };
499
500    void handleOnUidStateChanged(int uid, int procState) {
501        if (DEBUG_PROCSTATE) {
502            Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
503        }
504        synchronized (mLock) {
505            mUidState.put(uid, procState);
506
507            // We need to keep track of last time an app comes to foreground.
508            // See ShortcutPackage.getApiCallCount() for how it's used.
509            // It doesn't have to be persisted, but it needs to be the elapsed time.
510            if (isProcessStateForeground(procState)) {
511                mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime());
512            }
513        }
514    }
515
516    private boolean isProcessStateForeground(int processState) {
517        return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
518    }
519
520    boolean isUidForegroundLocked(int uid) {
521        if (uid == Process.SYSTEM_UID) {
522            // IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
523            // so it's foreground anyway.
524            return true;
525        }
526        // First, check with the local cache.
527        if (isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE))) {
528            return true;
529        }
530        // If the cache says background, reach out to AM.  Since it'll internally need to hold
531        // the AM lock, we use it as a last resort.
532        return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid));
533    }
534
535    long getUidLastForegroundElapsedTimeLocked(int uid) {
536        return mUidLastForegroundElapsedTime.get(uid);
537    }
538
539    /**
540     * System service lifecycle.
541     */
542    public static final class Lifecycle extends SystemService {
543        final ShortcutService mService;
544
545        public Lifecycle(Context context) {
546            super(context);
547            mService = new ShortcutService(context);
548        }
549
550        @Override
551        public void onStart() {
552            publishBinderService(Context.SHORTCUT_SERVICE, mService);
553        }
554
555        @Override
556        public void onBootPhase(int phase) {
557            mService.onBootPhase(phase);
558        }
559
560        @Override
561        public void onStopUser(int userHandle) {
562            mService.handleStopUser(userHandle);
563        }
564
565        @Override
566        public void onUnlockUser(int userId) {
567            mService.handleUnlockUser(userId);
568        }
569    }
570
571    /** lifecycle event */
572    void onBootPhase(int phase) {
573        if (DEBUG) {
574            Slog.d(TAG, "onBootPhase: " + phase);
575        }
576        switch (phase) {
577            case SystemService.PHASE_LOCK_SETTINGS_READY:
578                initialize();
579                break;
580            case SystemService.PHASE_BOOT_COMPLETED:
581                mBootCompleted.set(true);
582                break;
583        }
584    }
585
586    /** lifecycle event */
587    void handleUnlockUser(int userId) {
588        if (DEBUG) {
589        Slog.d(TAG, "handleUnlockUser: user=" + userId);
590        }
591        synchronized (mLock) {
592            mUnlockedUsers.put(userId, true);
593        }
594
595        // Preload the user data.
596        // Note, we don't use mHandler here but instead just start a new thread.
597        // This is because mHandler (which uses com.android.internal.os.BackgroundThread) is very
598        // busy at this point and this could take hundreds of milliseconds, which would be too
599        // late since the launcher would already have started.
600        // So we just create a new thread.  This code runs rarely, so we don't use a thread pool
601        // or anything.
602        final long start = injectElapsedRealtime();
603        injectRunOnNewThread(() -> {
604            synchronized (mLock) {
605                logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start);
606                getUserShortcutsLocked(userId);
607            }
608        });
609    }
610
611    /** lifecycle event */
612    void handleStopUser(int userId) {
613        if (DEBUG) {
614            Slog.d(TAG, "handleStopUser: user=" + userId);
615        }
616        synchronized (mLock) {
617            unloadUserLocked(userId);
618
619            mUnlockedUsers.put(userId, false);
620        }
621    }
622
623    private void unloadUserLocked(int userId) {
624        if (DEBUG) {
625            Slog.d(TAG, "unloadUserLocked: user=" + userId);
626        }
627        // Save all dirty information.
628        saveDirtyInfo();
629
630        // Unload
631        mUsers.delete(userId);
632    }
633
634    /** Return the base state file name */
635    private AtomicFile getBaseStateFile() {
636        final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
637        path.mkdirs();
638        return new AtomicFile(path);
639    }
640
641    /**
642     * Init the instance. (load the state file, etc)
643     */
644    private void initialize() {
645        synchronized (mLock) {
646            loadConfigurationLocked();
647            loadBaseStateLocked();
648        }
649    }
650
651    /**
652     * Load the configuration from Settings.
653     */
654    private void loadConfigurationLocked() {
655        updateConfigurationLocked(injectShortcutManagerConstants());
656    }
657
658    /**
659     * Load the configuration from Settings.
660     */
661    @VisibleForTesting
662    boolean updateConfigurationLocked(String config) {
663        boolean result = true;
664
665        final KeyValueListParser parser = new KeyValueListParser(',');
666        try {
667            parser.setString(config);
668        } catch (IllegalArgumentException e) {
669            // Failed to parse the settings string, log this and move on
670            // with defaults.
671            Slog.e(TAG, "Bad shortcut manager settings", e);
672            result = false;
673        }
674
675        mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
676                DEFAULT_SAVE_DELAY_MS));
677
678        mResetInterval = Math.max(1, parser.getLong(
679                ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
680                * 1000L);
681
682        mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
683                ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
684
685        mMaxShortcuts = Math.max(0, (int) parser.getLong(
686                ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP));
687
688        final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
689                ? (int) parser.getLong(
690                ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
691                DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
692                : (int) parser.getLong(
693                ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
694                DEFAULT_MAX_ICON_DIMENSION_DP));
695
696        mMaxIconDimension = injectDipToPixel(iconDimensionDp);
697
698        mIconPersistFormat = CompressFormat.valueOf(
699                parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
700
701        mIconPersistQuality = (int) parser.getLong(
702                ConfigConstants.KEY_ICON_QUALITY,
703                DEFAULT_ICON_PERSIST_QUALITY);
704
705        return result;
706    }
707
708    @VisibleForTesting
709    String injectShortcutManagerConstants() {
710        return android.provider.Settings.Global.getString(
711                mContext.getContentResolver(),
712                android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
713    }
714
715    @VisibleForTesting
716    int injectDipToPixel(int dip) {
717        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
718                mContext.getResources().getDisplayMetrics());
719    }
720
721    // === Persisting ===
722
723    @Nullable
724    static String parseStringAttribute(XmlPullParser parser, String attribute) {
725        return parser.getAttributeValue(null, attribute);
726    }
727
728    static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) {
729        return parseLongAttribute(parser, attribute) == 1;
730    }
731
732    static int parseIntAttribute(XmlPullParser parser, String attribute) {
733        return (int) parseLongAttribute(parser, attribute);
734    }
735
736    static int parseIntAttribute(XmlPullParser parser, String attribute, int def) {
737        return (int) parseLongAttribute(parser, attribute, def);
738    }
739
740    static long parseLongAttribute(XmlPullParser parser, String attribute) {
741        return parseLongAttribute(parser, attribute, 0);
742    }
743
744    static long parseLongAttribute(XmlPullParser parser, String attribute, long def) {
745        final String value = parseStringAttribute(parser, attribute);
746        if (TextUtils.isEmpty(value)) {
747            return def;
748        }
749        try {
750            return Long.parseLong(value);
751        } catch (NumberFormatException e) {
752            Slog.e(TAG, "Error parsing long " + value);
753            return def;
754        }
755    }
756
757    @Nullable
758    static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
759        final String value = parseStringAttribute(parser, attribute);
760        if (TextUtils.isEmpty(value)) {
761            return null;
762        }
763        return ComponentName.unflattenFromString(value);
764    }
765
766    @Nullable
767    static Intent parseIntentAttributeNoDefault(XmlPullParser parser, String attribute) {
768        final String value = parseStringAttribute(parser, attribute);
769        Intent parsed = null;
770        if (!TextUtils.isEmpty(value)) {
771            try {
772                parsed = Intent.parseUri(value, /* flags =*/ 0);
773            } catch (URISyntaxException e) {
774                Slog.e(TAG, "Error parsing intent", e);
775            }
776        }
777        return parsed;
778    }
779
780    @Nullable
781    static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
782        Intent parsed = parseIntentAttributeNoDefault(parser, attribute);
783        if (parsed == null) {
784            // Default intent.
785            parsed = new Intent(Intent.ACTION_VIEW);
786        }
787        return parsed;
788    }
789
790    static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
791        if (TextUtils.isEmpty(value)) return;
792
793        out.startTag(null, tag);
794        out.attribute(null, ATTR_VALUE, value);
795        out.endTag(null, tag);
796    }
797
798    static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
799        writeTagValue(out, tag, Long.toString(value));
800    }
801
802    static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
803        if (name == null) return;
804        writeTagValue(out, tag, name.flattenToString());
805    }
806
807    static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
808            throws IOException, XmlPullParserException {
809        if (bundle == null) return;
810
811        out.startTag(null, tag);
812        bundle.saveToXml(out);
813        out.endTag(null, tag);
814    }
815
816    static void writeAttr(XmlSerializer out, String name, CharSequence value) throws IOException {
817        if (TextUtils.isEmpty(value)) return;
818
819        out.attribute(null, name, value.toString());
820    }
821
822    static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
823        writeAttr(out, name, String.valueOf(value));
824    }
825
826    static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
827        if (value) {
828            writeAttr(out, name, "1");
829        }
830    }
831
832    static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
833        if (comp == null) return;
834        writeAttr(out, name, comp.flattenToString());
835    }
836
837    static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
838        if (intent == null) return;
839
840        writeAttr(out, name, intent.toUri(/* flags =*/ 0));
841    }
842
843    @VisibleForTesting
844    void saveBaseStateLocked() {
845        final AtomicFile file = getBaseStateFile();
846        if (DEBUG) {
847            Slog.d(TAG, "Saving to " + file.getBaseFile());
848        }
849
850        FileOutputStream outs = null;
851        try {
852            outs = file.startWrite();
853
854            // Write to XML
855            XmlSerializer out = new FastXmlSerializer();
856            out.setOutput(outs, StandardCharsets.UTF_8.name());
857            out.startDocument(null, true);
858            out.startTag(null, TAG_ROOT);
859
860            // Body.
861            writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
862
863            // Epilogue.
864            out.endTag(null, TAG_ROOT);
865            out.endDocument();
866
867            // Close.
868            file.finishWrite(outs);
869        } catch (IOException e) {
870            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
871            file.failWrite(outs);
872        }
873    }
874
875    private void loadBaseStateLocked() {
876        mRawLastResetTime = 0;
877
878        final AtomicFile file = getBaseStateFile();
879        if (DEBUG) {
880            Slog.d(TAG, "Loading from " + file.getBaseFile());
881        }
882        try (FileInputStream in = file.openRead()) {
883            XmlPullParser parser = Xml.newPullParser();
884            parser.setInput(in, StandardCharsets.UTF_8.name());
885
886            int type;
887            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
888                if (type != XmlPullParser.START_TAG) {
889                    continue;
890                }
891                final int depth = parser.getDepth();
892                // Check the root tag
893                final String tag = parser.getName();
894                if (depth == 1) {
895                    if (!TAG_ROOT.equals(tag)) {
896                        Slog.e(TAG, "Invalid root tag: " + tag);
897                        return;
898                    }
899                    continue;
900                }
901                // Assume depth == 2
902                switch (tag) {
903                    case TAG_LAST_RESET_TIME:
904                        mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
905                        break;
906                    default:
907                        Slog.e(TAG, "Invalid tag: " + tag);
908                        break;
909                }
910            }
911        } catch (FileNotFoundException e) {
912            // Use the default
913        } catch (IOException | XmlPullParserException e) {
914            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
915
916            mRawLastResetTime = 0;
917        }
918        // Adjust the last reset time.
919        getLastResetTimeLocked();
920    }
921
922    @VisibleForTesting
923    final File getUserFile(@UserIdInt int userId) {
924        return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
925    }
926
927    private void saveUserLocked(@UserIdInt int userId) {
928        final File path = getUserFile(userId);
929        if (DEBUG) {
930            Slog.d(TAG, "Saving to " + path);
931        }
932
933        mShortcutBitmapSaver.waitForAllSavesLocked();
934
935        path.getParentFile().mkdirs();
936        final AtomicFile file = new AtomicFile(path);
937        FileOutputStream os = null;
938        try {
939            os = file.startWrite();
940
941            saveUserInternalLocked(userId, os, /* forBackup= */ false);
942
943            file.finishWrite(os);
944
945            // Remove all dangling bitmap files.
946            cleanupDanglingBitmapDirectoriesLocked(userId);
947        } catch (XmlPullParserException | IOException e) {
948            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
949            file.failWrite(os);
950        }
951    }
952
953    private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
954            boolean forBackup) throws IOException, XmlPullParserException {
955
956        final BufferedOutputStream bos = new BufferedOutputStream(os);
957
958        // Write to XML
959        XmlSerializer out = new FastXmlSerializer();
960        out.setOutput(bos, StandardCharsets.UTF_8.name());
961        out.startDocument(null, true);
962
963        getUserShortcutsLocked(userId).saveToXml(out, forBackup);
964
965        out.endDocument();
966
967        bos.flush();
968        os.flush();
969    }
970
971    static IOException throwForInvalidTag(int depth, String tag) throws IOException {
972        throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
973    }
974
975    static void warnForInvalidTag(int depth, String tag) throws IOException {
976        Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
977    }
978
979    @Nullable
980    private ShortcutUser loadUserLocked(@UserIdInt int userId) {
981        final File path = getUserFile(userId);
982        if (DEBUG) {
983            Slog.d(TAG, "Loading from " + path);
984        }
985        final AtomicFile file = new AtomicFile(path);
986
987        final FileInputStream in;
988        try {
989            in = file.openRead();
990        } catch (FileNotFoundException e) {
991            if (DEBUG) {
992                Slog.d(TAG, "Not found " + path);
993            }
994            return null;
995        }
996        try {
997            final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
998            return ret;
999        } catch (IOException | XmlPullParserException | InvalidFileFormatException e) {
1000            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
1001            return null;
1002        } finally {
1003            IoUtils.closeQuietly(in);
1004        }
1005    }
1006
1007    private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
1008            boolean fromBackup) throws XmlPullParserException, IOException,
1009            InvalidFileFormatException {
1010
1011        final BufferedInputStream bis = new BufferedInputStream(is);
1012
1013        ShortcutUser ret = null;
1014        XmlPullParser parser = Xml.newPullParser();
1015        parser.setInput(bis, StandardCharsets.UTF_8.name());
1016
1017        int type;
1018        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
1019            if (type != XmlPullParser.START_TAG) {
1020                continue;
1021            }
1022            final int depth = parser.getDepth();
1023
1024            final String tag = parser.getName();
1025            if (DEBUG_LOAD) {
1026                Slog.d(TAG, String.format("depth=%d type=%d name=%s",
1027                        depth, type, tag));
1028            }
1029            if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
1030                ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
1031                continue;
1032            }
1033            throwForInvalidTag(depth, tag);
1034        }
1035        return ret;
1036    }
1037
1038    private void scheduleSaveBaseState() {
1039        scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
1040    }
1041
1042    void scheduleSaveUser(@UserIdInt int userId) {
1043        scheduleSaveInner(userId);
1044    }
1045
1046    // In order to re-schedule, we need to reuse the same instance, so keep it in final.
1047    private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo;
1048
1049    private void scheduleSaveInner(@UserIdInt int userId) {
1050        if (DEBUG) {
1051            Slog.d(TAG, "Scheduling to save for " + userId);
1052        }
1053        synchronized (mLock) {
1054            if (!mDirtyUserIds.contains(userId)) {
1055                mDirtyUserIds.add(userId);
1056            }
1057        }
1058        // If already scheduled, remove that and re-schedule in N seconds.
1059        mHandler.removeCallbacks(mSaveDirtyInfoRunner);
1060        mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
1061    }
1062
1063    @VisibleForTesting
1064    void saveDirtyInfo() {
1065        if (DEBUG) {
1066            Slog.d(TAG, "saveDirtyInfo");
1067        }
1068        try {
1069            synchronized (mLock) {
1070                for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
1071                    final int userId = mDirtyUserIds.get(i);
1072                    if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
1073                        saveBaseStateLocked();
1074                    } else {
1075                        saveUserLocked(userId);
1076                    }
1077                }
1078                mDirtyUserIds.clear();
1079            }
1080        } catch (Exception e) {
1081            wtf("Exception in saveDirtyInfo", e);
1082        }
1083    }
1084
1085    /** Return the last reset time. */
1086    long getLastResetTimeLocked() {
1087        updateTimesLocked();
1088        return mRawLastResetTime;
1089    }
1090
1091    /** Return the next reset time. */
1092    long getNextResetTimeLocked() {
1093        updateTimesLocked();
1094        return mRawLastResetTime + mResetInterval;
1095    }
1096
1097    static boolean isClockValid(long time) {
1098        return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
1099    }
1100
1101    /**
1102     * Update the last reset time.
1103     */
1104    private void updateTimesLocked() {
1105
1106        final long now = injectCurrentTimeMillis();
1107
1108        final long prevLastResetTime = mRawLastResetTime;
1109
1110        if (mRawLastResetTime == 0) { // first launch.
1111            // TODO Randomize??
1112            mRawLastResetTime = now;
1113        } else if (now < mRawLastResetTime) {
1114            // Clock rewound.
1115            if (isClockValid(now)) {
1116                Slog.w(TAG, "Clock rewound");
1117                // TODO Randomize??
1118                mRawLastResetTime = now;
1119            }
1120        } else {
1121            if ((mRawLastResetTime + mResetInterval) <= now) {
1122                final long offset = mRawLastResetTime % mResetInterval;
1123                mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
1124            }
1125        }
1126        if (prevLastResetTime != mRawLastResetTime) {
1127            scheduleSaveBaseState();
1128        }
1129    }
1130
1131    // Requires mLock held, but "Locked" prefix would look weired so we just say "L".
1132    protected boolean isUserUnlockedL(@UserIdInt int userId) {
1133        // First, check the local copy.
1134        if (mUnlockedUsers.get(userId)) {
1135            return true;
1136        }
1137        // If the local copy says the user is locked, check with AM for the actual state, since
1138        // the user might just have been unlocked.
1139        // Note we just don't use isUserUnlockingOrUnlocked() here, because it'll return false
1140        // when the user is STOPPING, which we still want to consider as "unlocked".
1141        final long token = injectClearCallingIdentity();
1142        try {
1143            return mUserManager.isUserUnlockingOrUnlocked(userId);
1144        } finally {
1145            injectRestoreCallingIdentity(token);
1146        }
1147    }
1148
1149    // Requires mLock held, but "Locked" prefix would look weired so we jsut say "L".
1150    void throwIfUserLockedL(@UserIdInt int userId) {
1151        if (!isUserUnlockedL(userId)) {
1152            throw new IllegalStateException("User " + userId + " is locked or not running");
1153        }
1154    }
1155
1156    @GuardedBy("mLock")
1157    @NonNull
1158    private boolean isUserLoadedLocked(@UserIdInt int userId) {
1159        return mUsers.get(userId) != null;
1160    }
1161
1162    /** Return the per-user state. */
1163    @GuardedBy("mLock")
1164    @NonNull
1165    ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
1166        if (!isUserUnlockedL(userId)) {
1167            wtf("User still locked");
1168        }
1169
1170        ShortcutUser userPackages = mUsers.get(userId);
1171        if (userPackages == null) {
1172            userPackages = loadUserLocked(userId);
1173            if (userPackages == null) {
1174                userPackages = new ShortcutUser(this, userId);
1175            }
1176            mUsers.put(userId, userPackages);
1177
1178            // Also when a user's data is first accessed, scan all packages.
1179            checkPackageChanges(userId);
1180        }
1181        return userPackages;
1182    }
1183
1184    void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
1185        for (int i = mUsers.size() - 1; i >= 0; i--) {
1186            c.accept(mUsers.valueAt(i));
1187        }
1188    }
1189
1190    /**
1191     * Return the per-user per-package state.  If the caller is a publisher, use
1192     * {@link #getPackageShortcutsForPublisherLocked} instead.
1193     */
1194    @GuardedBy("mLock")
1195    @NonNull
1196    ShortcutPackage getPackageShortcutsLocked(
1197            @NonNull String packageName, @UserIdInt int userId) {
1198        return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
1199    }
1200
1201    /** Return the per-user per-package state.  Use this when the caller is a publisher. */
1202    @GuardedBy("mLock")
1203    @NonNull
1204    ShortcutPackage getPackageShortcutsForPublisherLocked(
1205            @NonNull String packageName, @UserIdInt int userId) {
1206        final ShortcutPackage ret = getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
1207        ret.getUser().onCalledByPublisher(packageName);
1208        return ret;
1209    }
1210
1211    @GuardedBy("mLock")
1212    @NonNull
1213    ShortcutLauncher getLauncherShortcutsLocked(
1214            @NonNull String packageName, @UserIdInt int ownerUserId,
1215            @UserIdInt int launcherUserId) {
1216        return getUserShortcutsLocked(ownerUserId)
1217                .getLauncherShortcuts(packageName, launcherUserId);
1218    }
1219
1220    // === Caller validation ===
1221
1222    void removeIconLocked(ShortcutInfo shortcut) {
1223        mShortcutBitmapSaver.removeIcon(shortcut);
1224    }
1225
1226    public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
1227        final File packagePath = new File(getUserBitmapFilePath(userId), packageName);
1228        if (!packagePath.isDirectory()) {
1229            return;
1230        }
1231        if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) {
1232            Slog.w(TAG, "Unable to remove directory " + packagePath);
1233        }
1234    }
1235
1236    /**
1237     * Remove dangling bitmap files for a user.
1238     *
1239     * Note this method must be called with the lock held after calling
1240     * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
1241     * saves are going on.
1242     */
1243    private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
1244        if (DEBUG) {
1245            Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
1246        }
1247        final long start = injectElapsedRealtime();
1248
1249        final ShortcutUser user = getUserShortcutsLocked(userId);
1250
1251        final File bitmapDir = getUserBitmapFilePath(userId);
1252        final File[] children = bitmapDir.listFiles();
1253        if (children == null) {
1254            return;
1255        }
1256        for (File child : children) {
1257            if (!child.isDirectory()) {
1258                continue;
1259            }
1260            final String packageName = child.getName();
1261            if (DEBUG) {
1262                Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName);
1263            }
1264            if (!user.hasPackage(packageName)) {
1265                if (DEBUG) {
1266                    Slog.d(TAG, "Removing dangling bitmap directory: " + packageName);
1267                }
1268                cleanupBitmapsForPackage(userId, packageName);
1269            } else {
1270                cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
1271            }
1272        }
1273        logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
1274    }
1275
1276    /**
1277     * Remove dangling bitmap files for a package.
1278     *
1279     * Note this method must be called with the lock held after calling
1280     * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
1281     * saves are going on.
1282     */
1283    private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
1284            @NonNull String packageName, @NonNull File path) {
1285        final ArraySet<String> usedFiles =
1286                user.getPackageShortcuts(packageName).getUsedBitmapFiles();
1287
1288        for (File child : path.listFiles()) {
1289            if (!child.isFile()) {
1290                continue;
1291            }
1292            final String name = child.getName();
1293            if (!usedFiles.contains(name)) {
1294                if (DEBUG) {
1295                    Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
1296                }
1297                child.delete();
1298            }
1299        }
1300    }
1301
1302    @VisibleForTesting
1303    static class FileOutputStreamWithPath extends FileOutputStream {
1304        private final File mFile;
1305
1306        public FileOutputStreamWithPath(File file) throws FileNotFoundException {
1307            super(file);
1308            mFile = file;
1309        }
1310
1311        public File getFile() {
1312            return mFile;
1313        }
1314    }
1315
1316    /**
1317     * Build the cached bitmap filename for a shortcut icon.
1318     *
1319     * The filename will be based on the ID, except certain characters will be escaped.
1320     */
1321    FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
1322            throws IOException {
1323        final File packagePath = new File(getUserBitmapFilePath(userId),
1324                shortcut.getPackage());
1325        if (!packagePath.isDirectory()) {
1326            packagePath.mkdirs();
1327            if (!packagePath.isDirectory()) {
1328                throw new IOException("Unable to create directory " + packagePath);
1329            }
1330            SELinux.restorecon(packagePath);
1331        }
1332
1333        final String baseName = String.valueOf(injectCurrentTimeMillis());
1334        for (int suffix = 0; ; suffix++) {
1335            final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
1336            final File file = new File(packagePath, filename);
1337            if (!file.exists()) {
1338                if (DEBUG) {
1339                    Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
1340                }
1341                return new FileOutputStreamWithPath(file);
1342            }
1343        }
1344    }
1345
1346    void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) {
1347        if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
1348            return;
1349        }
1350
1351        final long token = injectClearCallingIdentity();
1352        try {
1353            // Clear icon info on the shortcut.
1354            removeIconLocked(shortcut);
1355
1356            final Icon icon = shortcut.getIcon();
1357            if (icon == null) {
1358                return; // has no icon
1359            }
1360            int maxIconDimension = mMaxIconDimension;
1361            Bitmap bitmap;
1362            try {
1363                switch (icon.getType()) {
1364                    case Icon.TYPE_RESOURCE: {
1365                        injectValidateIconResPackage(shortcut, icon);
1366
1367                        shortcut.setIconResourceId(icon.getResId());
1368                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
1369                        return;
1370                    }
1371                    case Icon.TYPE_BITMAP:
1372                        bitmap = icon.getBitmap(); // Don't recycle in this case.
1373                        break;
1374                    case Icon.TYPE_ADAPTIVE_BITMAP: {
1375                        bitmap = icon.getBitmap(); // Don't recycle in this case.
1376                        maxIconDimension *= (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
1377
1378                    }
1379                    default:
1380                        // This shouldn't happen because we've already validated the icon, but
1381                        // just in case.
1382                        throw ShortcutInfo.getInvalidIconException();
1383                }
1384                mShortcutBitmapSaver.saveBitmapLocked(shortcut,
1385                        maxIconDimension, mIconPersistFormat, mIconPersistQuality);
1386            } finally {
1387                // Once saved, we won't use the original icon information, so null it out.
1388                shortcut.clearIcon();
1389            }
1390        } finally {
1391            injectRestoreCallingIdentity(token);
1392        }
1393    }
1394
1395    // Unfortunately we can't do this check in unit tests because we fake creator package names,
1396    // so override in unit tests.
1397    // TODO CTS this case.
1398    void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
1399        if (!shortcut.getPackage().equals(icon.getResPackage())) {
1400            throw new IllegalArgumentException(
1401                    "Icon resource must reside in shortcut owner package");
1402        }
1403    }
1404
1405    static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
1406        // Original width/height.
1407        final int ow = in.getWidth();
1408        final int oh = in.getHeight();
1409        if ((ow <= maxSize) && (oh <= maxSize)) {
1410            if (DEBUG) {
1411                Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
1412            }
1413            return in;
1414        }
1415        final int longerDimension = Math.max(ow, oh);
1416
1417        // New width and height.
1418        final int nw = ow * maxSize / longerDimension;
1419        final int nh = oh * maxSize / longerDimension;
1420        if (DEBUG) {
1421            Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
1422                    ow, oh, nw, nh));
1423        }
1424
1425        final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
1426        final Canvas c = new Canvas(scaledBitmap);
1427
1428        final RectF dst = new RectF(0, 0, nw, nh);
1429
1430        c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
1431
1432        return scaledBitmap;
1433    }
1434
1435    /**
1436     * For a shortcut, update all resource names from resource IDs, and also update all
1437     * resource-based strings.
1438     */
1439    void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) {
1440        final Resources publisherRes = injectGetResourcesForApplicationAsUser(
1441                si.getPackage(), si.getUserId());
1442        if (publisherRes != null) {
1443            final long start = injectElapsedRealtime();
1444            try {
1445                si.lookupAndFillInResourceNames(publisherRes);
1446            } finally {
1447                logDurationStat(Stats.RESOURCE_NAME_LOOKUP, start);
1448            }
1449            si.resolveResourceStrings(publisherRes);
1450        }
1451    }
1452
1453    // === Caller validation ===
1454
1455    private boolean isCallerSystem() {
1456        final int callingUid = injectBinderCallingUid();
1457        return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
1458    }
1459
1460    private boolean isCallerShell() {
1461        final int callingUid = injectBinderCallingUid();
1462        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
1463    }
1464
1465    private void enforceSystemOrShell() {
1466        if (!(isCallerSystem() || isCallerShell())) {
1467            throw new SecurityException("Caller must be system or shell");
1468        }
1469    }
1470
1471    private void enforceShell() {
1472        if (!isCallerShell()) {
1473            throw new SecurityException("Caller must be shell");
1474        }
1475    }
1476
1477    private void enforceSystem() {
1478        if (!isCallerSystem()) {
1479            throw new SecurityException("Caller must be system");
1480        }
1481    }
1482
1483    private void enforceResetThrottlingPermission() {
1484        if (isCallerSystem()) {
1485            return;
1486        }
1487        enforceCallingOrSelfPermission(
1488                android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null);
1489    }
1490
1491    private void enforceCallingOrSelfPermission(
1492            @NonNull String permission, @Nullable String message) {
1493        if (isCallerSystem()) {
1494            return;
1495        }
1496        injectEnforceCallingPermission(permission, message);
1497    }
1498
1499    /**
1500     * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse
1501     * mockito.  So instead we extracted it here and override it in the tests.
1502     */
1503    @VisibleForTesting
1504    void injectEnforceCallingPermission(
1505            @NonNull String permission, @Nullable String message) {
1506        mContext.enforceCallingPermission(permission, message);
1507    }
1508
1509    private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
1510        Preconditions.checkStringNotEmpty(packageName, "packageName");
1511
1512        if (isCallerSystem()) {
1513            return; // no check
1514        }
1515
1516        final int callingUid = injectBinderCallingUid();
1517
1518        // Otherwise, make sure the arguments are valid.
1519        if (UserHandle.getUserId(callingUid) != userId) {
1520            throw new SecurityException("Invalid user-ID");
1521        }
1522        if (injectGetPackageUid(packageName, userId) != callingUid) {
1523            throw new SecurityException("Calling package name mismatch");
1524        }
1525        Preconditions.checkState(!isEphemeralApp(packageName, userId),
1526                "Ephemeral apps can't use ShortcutManager");
1527    }
1528
1529    // Overridden in unit tests to execute r synchronously.
1530    void injectPostToHandler(Runnable r) {
1531        mHandler.post(r);
1532    }
1533
1534    void injectRunOnNewThread(Runnable r) {
1535        new Thread(r).start();
1536    }
1537
1538    /**
1539     * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
1540     *                                  {@link #getMaxActivityShortcuts()}.
1541     */
1542    void enforceMaxActivityShortcuts(int numShortcuts) {
1543        if (numShortcuts > mMaxShortcuts) {
1544            throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
1545        }
1546    }
1547
1548    /**
1549     * Return the max number of dynamic + manifest shortcuts for each launcher icon.
1550     */
1551    int getMaxActivityShortcuts() {
1552        return mMaxShortcuts;
1553    }
1554
1555    /**
1556     * - Sends a notification to LauncherApps
1557     * - Write to file
1558     */
1559    void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) {
1560        notifyListeners(packageName, userId);
1561        scheduleSaveUser(userId);
1562    }
1563
1564    private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
1565        if (DEBUG) {
1566            Slog.d(TAG, String.format(
1567                    "Shortcut changes: package=%s, user=%d", packageName, userId));
1568        }
1569        injectPostToHandler(() -> {
1570            try {
1571                final ArrayList<ShortcutChangeListener> copy;
1572                synchronized (mLock) {
1573                    if (!isUserUnlockedL(userId)) {
1574                        return;
1575                    }
1576
1577                    copy = new ArrayList<>(mListeners);
1578                }
1579                // Note onShortcutChanged() needs to be called with the system service permissions.
1580                for (int i = copy.size() - 1; i >= 0; i--) {
1581                    copy.get(i).onShortcutChanged(packageName, userId);
1582                }
1583            } catch (Exception ignore) {
1584            }
1585        });
1586    }
1587
1588    /**
1589     * Clean up / validate an incoming shortcut.
1590     * - Make sure all mandatory fields are set.
1591     * - Make sure the intent's extras are persistable, and them to set
1592     * {@link ShortcutInfo#mIntentPersistableExtrases}.  Also clear its extras.
1593     * - Clear flags.
1594     */
1595    private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate,
1596            boolean forPinRequest) {
1597        if (shortcut.isReturnedByServer()) {
1598            Log.w(TAG,
1599                    "Re-publishing ShortcutInfo returned by server is not supported."
1600                    + " Some information such as icon may lost from shortcut.");
1601        }
1602        Preconditions.checkNotNull(shortcut, "Null shortcut detected");
1603        if (shortcut.getActivity() != null) {
1604            Preconditions.checkState(
1605                    shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
1606                    "Cannot publish shortcut: activity " + shortcut.getActivity() + " does not"
1607                    + " belong to package " + shortcut.getPackage());
1608            Preconditions.checkState(
1609                    injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()),
1610                    "Cannot publish shortcut: activity " + shortcut.getActivity() + " is not"
1611                            + " main activity");
1612        }
1613
1614        if (!forUpdate) {
1615            shortcut.enforceMandatoryFields(/* forPinned= */ forPinRequest);
1616            if (!forPinRequest) {
1617                Preconditions.checkState(shortcut.getActivity() != null,
1618                        "Cannot publish shortcut: target activity is not set");
1619            }
1620        }
1621        if (shortcut.getIcon() != null) {
1622            ShortcutInfo.validateIcon(shortcut.getIcon());
1623        }
1624
1625        shortcut.replaceFlags(0);
1626    }
1627
1628    private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
1629        fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false);
1630    }
1631
1632    public void validateShortcutForPinRequest(@NonNull ShortcutInfo shortcut) {
1633        fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false, /*forPinRequest=*/ true);
1634    }
1635
1636    /**
1637     * When a shortcut has no target activity, set the default one from the package.
1638     */
1639    private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) {
1640        ComponentName defaultActivity = null;
1641        for (int i = shortcuts.size() - 1; i >= 0; i--) {
1642            final ShortcutInfo si = shortcuts.get(i);
1643            if (si.getActivity() == null) {
1644                if (defaultActivity == null) {
1645                    defaultActivity = injectGetDefaultMainActivity(
1646                            si.getPackage(), si.getUserId());
1647                    Preconditions.checkState(defaultActivity != null,
1648                            "Launcher activity not found for package " + si.getPackage());
1649                }
1650                si.setActivity(defaultActivity);
1651            }
1652        }
1653    }
1654
1655    private void assignImplicitRanks(List<ShortcutInfo> shortcuts) {
1656        for (int i = shortcuts.size() - 1; i >= 0; i--) {
1657            shortcuts.get(i).setImplicitRank(i);
1658        }
1659    }
1660
1661    private List<ShortcutInfo> setReturnedByServer(List<ShortcutInfo> shortcuts) {
1662        for (int i = shortcuts.size() - 1; i >= 0; i--) {
1663            shortcuts.get(i).setReturnedByServer();
1664        }
1665        return shortcuts;
1666    }
1667
1668    // === APIs ===
1669
1670    @Override
1671    public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1672            @UserIdInt int userId) {
1673        verifyCaller(packageName, userId);
1674
1675        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1676        final int size = newShortcuts.size();
1677
1678        synchronized (mLock) {
1679            throwIfUserLockedL(userId);
1680
1681            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1682
1683            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1684
1685            fillInDefaultActivity(newShortcuts);
1686
1687            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
1688
1689            // Throttling.
1690            if (!ps.tryApiCall()) {
1691                return false;
1692            }
1693
1694            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1695            ps.clearAllImplicitRanks();
1696            assignImplicitRanks(newShortcuts);
1697
1698            for (int i = 0; i < size; i++) {
1699                fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
1700            }
1701
1702            // First, remove all un-pinned; dynamic shortcuts
1703            ps.deleteAllDynamicShortcuts();
1704
1705            // Then, add/update all.  We need to make sure to take over "pinned" flag.
1706            for (int i = 0; i < size; i++) {
1707                final ShortcutInfo newShortcut = newShortcuts.get(i);
1708                ps.addOrUpdateDynamicShortcut(newShortcut);
1709            }
1710
1711            // Lastly, adjust the ranks.
1712            ps.adjustRanks();
1713        }
1714        packageShortcutsChanged(packageName, userId);
1715
1716        verifyStates();
1717
1718        return true;
1719    }
1720
1721    @Override
1722    public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1723            @UserIdInt int userId) {
1724        verifyCaller(packageName, userId);
1725
1726        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1727        final int size = newShortcuts.size();
1728
1729        synchronized (mLock) {
1730            throwIfUserLockedL(userId);
1731
1732            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1733
1734            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1735
1736            // For update, don't fill in the default activity.  Having null activity means
1737            // "don't update the activity" here.
1738
1739            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
1740
1741            // Throttling.
1742            if (!ps.tryApiCall()) {
1743                return false;
1744            }
1745
1746            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1747            ps.clearAllImplicitRanks();
1748            assignImplicitRanks(newShortcuts);
1749
1750            for (int i = 0; i < size; i++) {
1751                final ShortcutInfo source = newShortcuts.get(i);
1752                fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
1753
1754                final ShortcutInfo target = ps.findShortcutById(source.getId());
1755                if (target == null) {
1756                    continue;
1757                }
1758
1759                if (target.isEnabled() != source.isEnabled()) {
1760                    Slog.w(TAG,
1761                            "ShortcutInfo.enabled cannot be changed with updateShortcuts()");
1762                }
1763
1764                // When updating the rank, we need to insert between existing ranks, so set
1765                // this setRankChanged, and also copy the implicit rank fo adjustRanks().
1766                if (source.hasRank()) {
1767                    target.setRankChanged();
1768                    target.setImplicitRank(source.getImplicitRank());
1769                }
1770
1771                final boolean replacingIcon = (source.getIcon() != null);
1772                if (replacingIcon) {
1773                    removeIconLocked(target);
1774                }
1775
1776                // Note copyNonNullFieldsFrom() does the "updatable with?" check too.
1777                target.copyNonNullFieldsFrom(source);
1778                target.setTimestamp(injectCurrentTimeMillis());
1779
1780                if (replacingIcon) {
1781                    saveIconAndFixUpShortcutLocked(target);
1782                }
1783
1784                // When we're updating any resource related fields, re-extract the res names and
1785                // the values.
1786                if (replacingIcon || source.hasStringResources()) {
1787                    fixUpShortcutResourceNamesAndValues(target);
1788                }
1789            }
1790
1791            // Lastly, adjust the ranks.
1792            ps.adjustRanks();
1793        }
1794        packageShortcutsChanged(packageName, userId);
1795
1796        verifyStates();
1797
1798        return true;
1799    }
1800
1801    @Override
1802    public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1803            @UserIdInt int userId) {
1804        verifyCaller(packageName, userId);
1805
1806        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1807        final int size = newShortcuts.size();
1808
1809        synchronized (mLock) {
1810            throwIfUserLockedL(userId);
1811
1812            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1813
1814            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1815
1816            fillInDefaultActivity(newShortcuts);
1817
1818            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
1819
1820            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1821            ps.clearAllImplicitRanks();
1822            assignImplicitRanks(newShortcuts);
1823
1824            // Throttling.
1825            if (!ps.tryApiCall()) {
1826                return false;
1827            }
1828            for (int i = 0; i < size; i++) {
1829                final ShortcutInfo newShortcut = newShortcuts.get(i);
1830
1831                // Validate the shortcut.
1832                fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
1833
1834                // When ranks are changing, we need to insert between ranks, so set the
1835                // "rank changed" flag.
1836                newShortcut.setRankChanged();
1837
1838                // Add it.
1839                ps.addOrUpdateDynamicShortcut(newShortcut);
1840            }
1841
1842            // Lastly, adjust the ranks.
1843            ps.adjustRanks();
1844        }
1845        packageShortcutsChanged(packageName, userId);
1846
1847        verifyStates();
1848
1849        return true;
1850    }
1851
1852    @Override
1853    public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut,
1854            IntentSender resultIntent, int userId) {
1855        Preconditions.checkNotNull(shortcut);
1856        Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
1857        return requestPinItem(packageName, userId, shortcut, null, null, resultIntent);
1858    }
1859
1860    @Override
1861    public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId)
1862            throws RemoteException {
1863        Preconditions.checkNotNull(shortcut);
1864        Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
1865        verifyCaller(packageName, userId);
1866
1867        final Intent ret;
1868        synchronized (mLock) {
1869            throwIfUserLockedL(userId);
1870
1871            // Send request to the launcher, if supported.
1872            ret = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId);
1873        }
1874
1875        verifyStates();
1876        return ret;
1877    }
1878
1879    /**
1880     * Handles {@link #requestPinShortcut} and {@link ShortcutServiceInternal#requestPinAppWidget}.
1881     * After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}.
1882     * Either {@param shortcut} or {@param appWidget} should be non-null.
1883     */
1884    private boolean requestPinItem(String packageName, int userId, ShortcutInfo shortcut,
1885            AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) {
1886        verifyCaller(packageName, userId);
1887
1888        final boolean ret;
1889        synchronized (mLock) {
1890            throwIfUserLockedL(userId);
1891
1892            Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()),
1893                    "Calling application must have a foreground activity or a foreground service");
1894
1895            // Send request to the launcher, if supported.
1896            ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras,
1897                    userId, resultIntent);
1898        }
1899
1900        verifyStates();
1901
1902        return ret;
1903    }
1904
1905    @Override
1906    public void disableShortcuts(String packageName, List shortcutIds,
1907            CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) {
1908        verifyCaller(packageName, userId);
1909        Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1910
1911        synchronized (mLock) {
1912            throwIfUserLockedL(userId);
1913
1914            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1915
1916            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1917
1918            final String disabledMessageString =
1919                    (disabledMessage == null) ? null : disabledMessage.toString();
1920
1921            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1922                ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
1923                        disabledMessageString, disabledMessageResId,
1924                        /* overrideImmutable=*/ false);
1925            }
1926
1927            // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
1928            ps.adjustRanks();
1929        }
1930        packageShortcutsChanged(packageName, userId);
1931
1932        verifyStates();
1933    }
1934
1935    @Override
1936    public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) {
1937        verifyCaller(packageName, userId);
1938        Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1939
1940        synchronized (mLock) {
1941            throwIfUserLockedL(userId);
1942
1943            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1944
1945            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1946
1947            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1948                ps.enableWithId((String) shortcutIds.get(i));
1949            }
1950        }
1951        packageShortcutsChanged(packageName, userId);
1952
1953        verifyStates();
1954    }
1955
1956    @Override
1957    public void removeDynamicShortcuts(String packageName, List shortcutIds,
1958            @UserIdInt int userId) {
1959        verifyCaller(packageName, userId);
1960        Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1961
1962        synchronized (mLock) {
1963            throwIfUserLockedL(userId);
1964
1965            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1966
1967            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1968
1969            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1970                ps.deleteDynamicWithId(
1971                        Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
1972            }
1973
1974            // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
1975            ps.adjustRanks();
1976        }
1977        packageShortcutsChanged(packageName, userId);
1978
1979        verifyStates();
1980    }
1981
1982    @Override
1983    public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
1984        verifyCaller(packageName, userId);
1985
1986        synchronized (mLock) {
1987            throwIfUserLockedL(userId);
1988
1989            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1990            ps.deleteAllDynamicShortcuts();
1991        }
1992        packageShortcutsChanged(packageName, userId);
1993
1994        verifyStates();
1995    }
1996
1997    @Override
1998    public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
1999            @UserIdInt int userId) {
2000        verifyCaller(packageName, userId);
2001
2002        synchronized (mLock) {
2003            throwIfUserLockedL(userId);
2004
2005            return getShortcutsWithQueryLocked(
2006                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
2007                    ShortcutInfo::isDynamic);
2008        }
2009    }
2010
2011    @Override
2012    public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
2013            @UserIdInt int userId) {
2014        verifyCaller(packageName, userId);
2015
2016        synchronized (mLock) {
2017            throwIfUserLockedL(userId);
2018
2019            return getShortcutsWithQueryLocked(
2020                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
2021                    ShortcutInfo::isManifestShortcut);
2022        }
2023    }
2024
2025    @Override
2026    public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
2027            @UserIdInt int userId) {
2028        verifyCaller(packageName, userId);
2029
2030        synchronized (mLock) {
2031            throwIfUserLockedL(userId);
2032
2033            return getShortcutsWithQueryLocked(
2034                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
2035                    ShortcutInfo::isPinned);
2036        }
2037    }
2038
2039    private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
2040            @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
2041
2042        final ArrayList<ShortcutInfo> ret = new ArrayList<>();
2043
2044        final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
2045        ps.findAll(ret, query, cloneFlags);
2046
2047        return new ParceledListSlice<>(setReturnedByServer(ret));
2048    }
2049
2050    @Override
2051    public int getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId)
2052            throws RemoteException {
2053        verifyCaller(packageName, userId);
2054
2055        return mMaxShortcuts;
2056    }
2057
2058    @Override
2059    public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
2060        verifyCaller(packageName, userId);
2061
2062        synchronized (mLock) {
2063            throwIfUserLockedL(userId);
2064
2065            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
2066            return mMaxUpdatesPerInterval - ps.getApiCallCount();
2067        }
2068    }
2069
2070    @Override
2071    public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
2072        verifyCaller(packageName, userId);
2073
2074        synchronized (mLock) {
2075            throwIfUserLockedL(userId);
2076
2077            return getNextResetTimeLocked();
2078        }
2079    }
2080
2081    @Override
2082    public int getIconMaxDimensions(String packageName, int userId) {
2083        verifyCaller(packageName, userId);
2084
2085        synchronized (mLock) {
2086            return mMaxIconDimension;
2087        }
2088    }
2089
2090    @Override
2091    public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
2092        verifyCaller(packageName, userId);
2093
2094        Preconditions.checkNotNull(shortcutId);
2095
2096        if (DEBUG) {
2097            Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
2098                    shortcutId, packageName, userId));
2099        }
2100
2101        synchronized (mLock) {
2102            throwIfUserLockedL(userId);
2103
2104            final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
2105
2106            if (ps.findShortcutById(shortcutId) == null) {
2107                Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
2108                        packageName, shortcutId));
2109                return;
2110            }
2111        }
2112
2113        final long token = injectClearCallingIdentity();
2114        try {
2115            mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
2116        } finally {
2117            injectRestoreCallingIdentity(token);
2118        }
2119    }
2120
2121    @Override
2122    public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
2123        final long token = injectClearCallingIdentity();
2124        try {
2125            return mShortcutRequestPinProcessor
2126                    .isRequestPinItemSupported(callingUserId, requestType);
2127        } finally {
2128            injectRestoreCallingIdentity(token);
2129        }
2130    }
2131
2132    /**
2133     * Reset all throttling, for developer options and command line.  Only system/shell can call
2134     * it.
2135     */
2136    @Override
2137    public void resetThrottling() {
2138        enforceSystemOrShell();
2139
2140        resetThrottlingInner(getCallingUserId());
2141    }
2142
2143    void resetThrottlingInner(@UserIdInt int userId) {
2144        synchronized (mLock) {
2145            if (!isUserUnlockedL(userId)) {
2146                Log.w(TAG, "User " + userId + " is locked or not running");
2147                return;
2148            }
2149
2150            getUserShortcutsLocked(userId).resetThrottling();
2151        }
2152        scheduleSaveUser(userId);
2153        Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId);
2154    }
2155
2156    void resetAllThrottlingInner() {
2157        synchronized (mLock) {
2158            mRawLastResetTime = injectCurrentTimeMillis();
2159        }
2160        scheduleSaveBaseState();
2161        Slog.i(TAG, "ShortcutManager: throttling counter reset for all users");
2162    }
2163
2164    @Override
2165    public void onApplicationActive(String packageName, int userId) {
2166        if (DEBUG) {
2167            Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userid=" + userId);
2168        }
2169        enforceResetThrottlingPermission();
2170
2171        synchronized (mLock) {
2172            if (!isUserUnlockedL(userId)) {
2173                // This is called by system UI, so no need to throw.  Just ignore.
2174                return;
2175            }
2176
2177            getPackageShortcutsLocked(packageName, userId)
2178                    .resetRateLimitingForCommandLineNoSaving();
2179            saveUserLocked(userId);
2180        }
2181    }
2182
2183    // We override this method in unit tests to do a simpler check.
2184    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
2185        final long start = injectElapsedRealtime();
2186        try {
2187            return hasShortcutHostPermissionInner(callingPackage, userId);
2188        } finally {
2189            logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
2190        }
2191    }
2192
2193    // This method is extracted so we can directly call this method from unit tests,
2194    // even when hasShortcutPermission() is overridden.
2195    @VisibleForTesting
2196    boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) {
2197        synchronized (mLock) {
2198            throwIfUserLockedL(userId);
2199
2200            final ShortcutUser user = getUserShortcutsLocked(userId);
2201
2202            // Always trust the cached component.
2203            final ComponentName cached = user.getCachedLauncher();
2204            if (cached != null) {
2205                if (cached.getPackageName().equals(packageName)) {
2206                    return true;
2207                }
2208            }
2209            // If the cached one doesn't match, then go ahead
2210
2211            final ComponentName detected = getDefaultLauncher(userId);
2212
2213            // Update the cache.
2214            user.setLauncher(detected);
2215            if (detected != null) {
2216                if (DEBUG) {
2217                    Slog.v(TAG, "Detected launcher: " + detected);
2218                }
2219                return detected.getPackageName().equals(packageName);
2220            } else {
2221                // Default launcher not found.
2222                return false;
2223            }
2224        }
2225    }
2226
2227    @Nullable
2228    ComponentName getDefaultLauncher(@UserIdInt int userId) {
2229        final long start = injectElapsedRealtime();
2230        final long token = injectClearCallingIdentity();
2231        try {
2232            synchronized (mLock) {
2233                throwIfUserLockedL(userId);
2234
2235                final ShortcutUser user = getUserShortcutsLocked(userId);
2236
2237                final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
2238
2239                // Default launcher from package manager.
2240                final long startGetHomeActivitiesAsUser = injectElapsedRealtime();
2241                final ComponentName defaultLauncher = mPackageManagerInternal
2242                        .getHomeActivitiesAsUser(allHomeCandidates, userId);
2243                logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
2244
2245                ComponentName detected = null;
2246                if (defaultLauncher != null) {
2247                    detected = defaultLauncher;
2248                    if (DEBUG) {
2249                        Slog.v(TAG, "Default launcher from PM: " + detected);
2250                    }
2251                } else {
2252                    detected = user.getLastKnownLauncher();
2253
2254                    if (detected != null) {
2255                        if (injectIsActivityEnabledAndExported(detected, userId)) {
2256                            if (DEBUG) {
2257                                Slog.v(TAG, "Cached launcher: " + detected);
2258                            }
2259                        } else {
2260                            Slog.w(TAG, "Cached launcher " + detected + " no longer exists");
2261                            detected = null;
2262                            user.clearLauncher();
2263                        }
2264                    }
2265                }
2266
2267                if (detected == null) {
2268                    // If we reach here, that means it's the first check since the user was created,
2269                    // and there's already multiple launchers and there's no default set.
2270                    // Find the system one with the highest priority.
2271                    // (We need to check the priority too because of FallbackHome in Settings.)
2272                    // If there's no system launcher yet, then no one can access shortcuts, until
2273                    // the user explicitly
2274                    final int size = allHomeCandidates.size();
2275
2276                    int lastPriority = Integer.MIN_VALUE;
2277                    for (int i = 0; i < size; i++) {
2278                        final ResolveInfo ri = allHomeCandidates.get(i);
2279                        if (!ri.activityInfo.applicationInfo.isSystemApp()) {
2280                            continue;
2281                        }
2282                        if (DEBUG) {
2283                            Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
2284                                    ri.activityInfo.getComponentName(), ri.priority));
2285                        }
2286                        if (ri.priority < lastPriority) {
2287                            continue;
2288                        }
2289                        detected = ri.activityInfo.getComponentName();
2290                        lastPriority = ri.priority;
2291                    }
2292                }
2293                return detected;
2294            }
2295        } finally {
2296            injectRestoreCallingIdentity(token);
2297            logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start);
2298        }
2299    }
2300
2301    // === House keeping ===
2302
2303    private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
2304            boolean appStillExists) {
2305        synchronized (mLock) {
2306            forEachLoadedUserLocked(user ->
2307                    cleanUpPackageLocked(packageName, user.getUserId(), packageUserId,
2308                            appStillExists));
2309        }
2310    }
2311
2312    /**
2313     * Remove all the information associated with a package.  This will really remove all the
2314     * information, including the restore information (i.e. it'll remove packages even if they're
2315     * shadow).
2316     *
2317     * This is called when an app is uninstalled, or an app gets "clear data"ed.
2318     */
2319    @VisibleForTesting
2320    void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
2321            boolean appStillExists) {
2322        final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
2323
2324        final ShortcutUser user = getUserShortcutsLocked(owningUserId);
2325        boolean doNotify = false;
2326
2327        // First, remove the package from the package list (if the package is a publisher).
2328        if (packageUserId == owningUserId) {
2329            if (user.removePackage(packageName) != null) {
2330                doNotify = true;
2331            }
2332        }
2333
2334        // Also remove from the launcher list (if the package is a launcher).
2335        user.removeLauncher(packageUserId, packageName);
2336
2337        // Then remove pinned shortcuts from all launchers.
2338        user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId));
2339
2340        // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous
2341        // step.  Remove them too.
2342        user.forAllPackages(p -> p.refreshPinnedFlags());
2343
2344        scheduleSaveUser(owningUserId);
2345
2346        if (doNotify) {
2347            notifyListeners(packageName, owningUserId);
2348        }
2349
2350        // If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts.
2351        if (appStillExists && (packageUserId == owningUserId)) {
2352            // This will do the notification and save when needed, so do it after the above
2353            // notifyListeners.
2354            user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
2355        }
2356
2357        if (!wasUserLoaded) {
2358            // Note this will execute the scheduled save.
2359            unloadUserLocked(owningUserId);
2360        }
2361    }
2362
2363    /**
2364     * Entry point from {@link LauncherApps}.
2365     */
2366    private class LocalService extends ShortcutServiceInternal {
2367
2368        @Override
2369        public List<ShortcutInfo> getShortcuts(int launcherUserId,
2370                @NonNull String callingPackage, long changedSince,
2371                @Nullable String packageName, @Nullable List<String> shortcutIds,
2372                @Nullable ComponentName componentName,
2373                int queryFlags, int userId) {
2374            final ArrayList<ShortcutInfo> ret = new ArrayList<>();
2375
2376            final boolean cloneKeyFieldOnly =
2377                    ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
2378            final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO
2379                    : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
2380            if (packageName == null) {
2381                shortcutIds = null; // LauncherAppsService already threw for it though.
2382            }
2383
2384            synchronized (mLock) {
2385                throwIfUserLockedL(userId);
2386                throwIfUserLockedL(launcherUserId);
2387
2388                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2389                        .attemptToRestoreIfNeededAndSave();
2390
2391                if (packageName != null) {
2392                    getShortcutsInnerLocked(launcherUserId,
2393                            callingPackage, packageName, shortcutIds, changedSince,
2394                            componentName, queryFlags, userId, ret, cloneFlag);
2395                } else {
2396                    final List<String> shortcutIdsF = shortcutIds;
2397                    getUserShortcutsLocked(userId).forAllPackages(p -> {
2398                        getShortcutsInnerLocked(launcherUserId,
2399                                callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
2400                                componentName, queryFlags, userId, ret, cloneFlag);
2401                    });
2402                }
2403            }
2404            return setReturnedByServer(ret);
2405        }
2406
2407        private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
2408                @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
2409                @Nullable ComponentName componentName, int queryFlags,
2410                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
2411            final ArraySet<String> ids = shortcutIds == null ? null
2412                    : new ArraySet<>(shortcutIds);
2413
2414            final ShortcutPackage p = getUserShortcutsLocked(userId)
2415                    .getPackageShortcutsIfExists(packageName);
2416            if (p == null) {
2417                return; // No need to instantiate ShortcutPackage.
2418            }
2419
2420            p.findAll(ret,
2421                    (ShortcutInfo si) -> {
2422                        if (si.getLastChangedTimestamp() < changedSince) {
2423                            return false;
2424                        }
2425                        if (ids != null && !ids.contains(si.getId())) {
2426                            return false;
2427                        }
2428                        if (componentName != null) {
2429                            if (si.getActivity() != null
2430                                    && !si.getActivity().equals(componentName)) {
2431                                return false;
2432                            }
2433                        }
2434                        if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
2435                                && si.isDynamic()) {
2436                            return true;
2437                        }
2438                        if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
2439                                && si.isPinned()) {
2440                            return true;
2441                        }
2442                        if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
2443                                && si.isManifestShortcut()) {
2444                            return true;
2445                        }
2446                        return false;
2447                    }, cloneFlag, callingPackage, launcherUserId);
2448        }
2449
2450        @Override
2451        public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
2452                @NonNull String packageName, @NonNull String shortcutId, int userId) {
2453            Preconditions.checkStringNotEmpty(packageName, "packageName");
2454            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
2455
2456            synchronized (mLock) {
2457                throwIfUserLockedL(userId);
2458                throwIfUserLockedL(launcherUserId);
2459
2460                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2461                        .attemptToRestoreIfNeededAndSave();
2462
2463                final ShortcutInfo si = getShortcutInfoLocked(
2464                        launcherUserId, callingPackage, packageName, shortcutId, userId);
2465                return si != null && si.isPinned();
2466            }
2467        }
2468
2469        private ShortcutInfo getShortcutInfoLocked(
2470                int launcherUserId, @NonNull String callingPackage,
2471                @NonNull String packageName, @NonNull String shortcutId, int userId) {
2472            Preconditions.checkStringNotEmpty(packageName, "packageName");
2473            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
2474
2475            throwIfUserLockedL(userId);
2476            throwIfUserLockedL(launcherUserId);
2477
2478            final ShortcutPackage p = getUserShortcutsLocked(userId)
2479                    .getPackageShortcutsIfExists(packageName);
2480            if (p == null) {
2481                return null;
2482            }
2483
2484            final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
2485            p.findAll(list,
2486                    (ShortcutInfo si) -> shortcutId.equals(si.getId()),
2487                    /* clone flags=*/ 0, callingPackage, launcherUserId);
2488            return list.size() == 0 ? null : list.get(0);
2489        }
2490
2491        @Override
2492        public void pinShortcuts(int launcherUserId,
2493                @NonNull String callingPackage, @NonNull String packageName,
2494                @NonNull List<String> shortcutIds, int userId) {
2495            // Calling permission must be checked by LauncherAppsImpl.
2496            Preconditions.checkStringNotEmpty(packageName, "packageName");
2497            Preconditions.checkNotNull(shortcutIds, "shortcutIds");
2498
2499            synchronized (mLock) {
2500                throwIfUserLockedL(userId);
2501                throwIfUserLockedL(launcherUserId);
2502
2503                final ShortcutLauncher launcher =
2504                        getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
2505                launcher.attemptToRestoreIfNeededAndSave();
2506
2507                launcher.pinShortcuts(userId, packageName, shortcutIds);
2508            }
2509            packageShortcutsChanged(packageName, userId);
2510
2511            verifyStates();
2512        }
2513
2514        @Override
2515        public Intent[] createShortcutIntents(int launcherUserId,
2516                @NonNull String callingPackage,
2517                @NonNull String packageName, @NonNull String shortcutId, int userId) {
2518            // Calling permission must be checked by LauncherAppsImpl.
2519            Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
2520            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
2521
2522            synchronized (mLock) {
2523                throwIfUserLockedL(userId);
2524                throwIfUserLockedL(launcherUserId);
2525
2526                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2527                        .attemptToRestoreIfNeededAndSave();
2528
2529                // Make sure the shortcut is actually visible to the launcher.
2530                final ShortcutInfo si = getShortcutInfoLocked(
2531                        launcherUserId, callingPackage, packageName, shortcutId, userId);
2532                // "si == null" should suffice here, but check the flags too just to make sure.
2533                if (si == null || !si.isEnabled() || !si.isAlive()) {
2534                    Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled");
2535                    return null;
2536                }
2537                return si.getIntents();
2538            }
2539        }
2540
2541        @Override
2542        public void addListener(@NonNull ShortcutChangeListener listener) {
2543            synchronized (mLock) {
2544                mListeners.add(Preconditions.checkNotNull(listener));
2545            }
2546        }
2547
2548        @Override
2549        public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
2550                @NonNull String packageName, @NonNull String shortcutId, int userId) {
2551            Preconditions.checkNotNull(callingPackage, "callingPackage");
2552            Preconditions.checkNotNull(packageName, "packageName");
2553            Preconditions.checkNotNull(shortcutId, "shortcutId");
2554
2555            synchronized (mLock) {
2556                throwIfUserLockedL(userId);
2557                throwIfUserLockedL(launcherUserId);
2558
2559                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2560                        .attemptToRestoreIfNeededAndSave();
2561
2562                final ShortcutPackage p = getUserShortcutsLocked(userId)
2563                        .getPackageShortcutsIfExists(packageName);
2564                if (p == null) {
2565                    return 0;
2566                }
2567
2568                final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
2569                return (shortcutInfo != null && shortcutInfo.hasIconResource())
2570                        ? shortcutInfo.getIconResourceId() : 0;
2571            }
2572        }
2573
2574        @Override
2575        public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
2576                @NonNull String callingPackage, @NonNull String packageName,
2577                @NonNull String shortcutId, int userId) {
2578            Preconditions.checkNotNull(callingPackage, "callingPackage");
2579            Preconditions.checkNotNull(packageName, "packageName");
2580            Preconditions.checkNotNull(shortcutId, "shortcutId");
2581
2582            synchronized (mLock) {
2583                throwIfUserLockedL(userId);
2584                throwIfUserLockedL(launcherUserId);
2585
2586                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2587                        .attemptToRestoreIfNeededAndSave();
2588
2589                final ShortcutPackage p = getUserShortcutsLocked(userId)
2590                        .getPackageShortcutsIfExists(packageName);
2591                if (p == null) {
2592                    return null;
2593                }
2594
2595                final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
2596                if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
2597                    return null;
2598                }
2599                final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo);
2600                if (path == null) {
2601                    Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
2602                    return null;
2603                }
2604                try {
2605                    return ParcelFileDescriptor.open(
2606                            new File(path),
2607                            ParcelFileDescriptor.MODE_READ_ONLY);
2608                } catch (FileNotFoundException e) {
2609                    Slog.e(TAG, "Icon file not found: " + path);
2610                    return null;
2611                }
2612            }
2613        }
2614
2615        @Override
2616        public boolean hasShortcutHostPermission(int launcherUserId,
2617                @NonNull String callingPackage) {
2618            return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
2619        }
2620
2621        @Override
2622        public boolean requestPinAppWidget(@NonNull String callingPackage,
2623                @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
2624                @Nullable IntentSender resultIntent, int userId) {
2625            Preconditions.checkNotNull(appWidget);
2626            return requestPinItem(callingPackage, userId, null, appWidget, extras, resultIntent);
2627        }
2628
2629        @Override
2630        public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
2631            return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType);
2632        }
2633    }
2634
2635    final BroadcastReceiver mReceiver = new BroadcastReceiver() {
2636        @Override
2637        public void onReceive(Context context, Intent intent) {
2638            if (!mBootCompleted.get()) {
2639                return; // Boot not completed, ignore the broadcast.
2640            }
2641            try {
2642                if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
2643                    handleLocaleChanged();
2644                }
2645            } catch (Exception e) {
2646                wtf("Exception in mReceiver.onReceive", e);
2647            }
2648        }
2649    };
2650
2651    void handleLocaleChanged() {
2652        if (DEBUG) {
2653            Slog.d(TAG, "handleLocaleChanged");
2654        }
2655        scheduleSaveBaseState();
2656
2657        synchronized (mLock) {
2658            final long token = injectClearCallingIdentity();
2659            try {
2660                forEachLoadedUserLocked(user -> user.detectLocaleChange());
2661            } finally {
2662                injectRestoreCallingIdentity(token);
2663            }
2664        }
2665    }
2666
2667    /**
2668     * Package event callbacks.
2669     */
2670    @VisibleForTesting
2671    final BroadcastReceiver mPackageMonitor = new BroadcastReceiver() {
2672        @Override
2673        public void onReceive(Context context, Intent intent) {
2674            final int userId  = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
2675            if (userId == UserHandle.USER_NULL) {
2676                Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent);
2677                return;
2678            }
2679
2680            final String action = intent.getAction();
2681
2682            // This is normally called on Handler, so clearCallingIdentity() isn't needed,
2683            // but we still check it in unit tests.
2684            final long token = injectClearCallingIdentity();
2685            try {
2686                synchronized (mLock) {
2687                    if (!isUserUnlockedL(userId)) {
2688                        if (DEBUG) {
2689                            Slog.d(TAG, "Ignoring package broadcast " + action
2690                                    + " for locked/stopped user " + userId);
2691                        }
2692                        return;
2693                    }
2694
2695                    // Whenever we get one of those package broadcasts, or get
2696                    // ACTION_PREFERRED_ACTIVITY_CHANGED, we purge the default launcher cache.
2697                    final ShortcutUser user = getUserShortcutsLocked(userId);
2698                    user.clearLauncher();
2699                }
2700                if (Intent.ACTION_PREFERRED_ACTIVITY_CHANGED.equals(action)) {
2701                    // Nothing farther to do.
2702                    return;
2703                }
2704
2705                final Uri intentUri = intent.getData();
2706                final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart()
2707                        : null;
2708                if (packageName == null) {
2709                    Slog.w(TAG, "Intent broadcast does not contain package name: " + intent);
2710                    return;
2711                }
2712
2713                final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
2714
2715                switch (action) {
2716                    case Intent.ACTION_PACKAGE_ADDED:
2717                        if (replacing) {
2718                            handlePackageUpdateFinished(packageName, userId);
2719                        } else {
2720                            handlePackageAdded(packageName, userId);
2721                        }
2722                        break;
2723                    case Intent.ACTION_PACKAGE_REMOVED:
2724                        if (!replacing) {
2725                            handlePackageRemoved(packageName, userId);
2726                        }
2727                        break;
2728                    case Intent.ACTION_PACKAGE_CHANGED:
2729                        handlePackageChanged(packageName, userId);
2730
2731                        break;
2732                    case Intent.ACTION_PACKAGE_DATA_CLEARED:
2733                        handlePackageDataCleared(packageName, userId);
2734                        break;
2735                }
2736            } catch (Exception e) {
2737                wtf("Exception in mPackageMonitor.onReceive", e);
2738            } finally {
2739                injectRestoreCallingIdentity(token);
2740            }
2741        }
2742    };
2743
2744    /**
2745     * Called when a user is unlocked.
2746     * - Check all known packages still exist, and otherwise perform cleanup.
2747     * - If a package still exists, check the version code.  If it's been updated, may need to
2748     * update timestamps of its shortcuts.
2749     */
2750    @VisibleForTesting
2751    void checkPackageChanges(@UserIdInt int ownerUserId) {
2752        if (DEBUG) {
2753            Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
2754        }
2755        if (injectIsSafeModeEnabled()) {
2756            Slog.i(TAG, "Safe mode, skipping checkPackageChanges()");
2757            return;
2758        }
2759
2760        final long start = injectElapsedRealtime();
2761        try {
2762            final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
2763
2764            synchronized (mLock) {
2765                final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
2766
2767                // Find packages that have been uninstalled.
2768                user.forAllPackageItems(spi -> {
2769                    if (spi.getPackageInfo().isShadow()) {
2770                        return; // Don't delete shadow information.
2771                    }
2772                    if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
2773                        if (DEBUG) {
2774                            Slog.d(TAG, "Uninstalled: " + spi.getPackageName()
2775                                    + " user " + spi.getPackageUserId());
2776                        }
2777                        gonePackages.add(PackageWithUser.of(spi));
2778                    }
2779                });
2780                if (gonePackages.size() > 0) {
2781                    for (int i = gonePackages.size() - 1; i >= 0; i--) {
2782                        final PackageWithUser pu = gonePackages.get(i);
2783                        cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId,
2784                                /* appStillExists = */ false);
2785                    }
2786                }
2787
2788                rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime());
2789            }
2790        } finally {
2791            logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
2792        }
2793        verifyStates();
2794    }
2795
2796    private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) {
2797        final ShortcutUser user = getUserShortcutsLocked(userId);
2798
2799        // Note after each OTA, we'll need to rescan all system apps, as their lastUpdateTime
2800        // is not reliable.
2801        final long now = injectCurrentTimeMillis();
2802        final boolean afterOta =
2803                !injectBuildFingerprint().equals(user.getLastAppScanOsFingerprint());
2804
2805        // Then for each installed app, publish manifest shortcuts when needed.
2806        forUpdatedPackages(userId, lastScanTime, afterOta, ai -> {
2807            user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId);
2808
2809            user.rescanPackageIfNeeded(ai.packageName, /* forceRescan= */ true);
2810        });
2811
2812        // Write the time just before the scan, because there may be apps that have just
2813        // been updated, and we want to catch them in the next time.
2814        user.setLastAppScanTime(now);
2815        user.setLastAppScanOsFingerprint(injectBuildFingerprint());
2816        scheduleSaveUser(userId);
2817    }
2818
2819    private void handlePackageAdded(String packageName, @UserIdInt int userId) {
2820        if (DEBUG) {
2821            Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
2822        }
2823        synchronized (mLock) {
2824            final ShortcutUser user = getUserShortcutsLocked(userId);
2825            user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
2826            user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
2827        }
2828        verifyStates();
2829    }
2830
2831    private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
2832        if (DEBUG) {
2833            Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
2834                    packageName, userId));
2835        }
2836        synchronized (mLock) {
2837            final ShortcutUser user = getUserShortcutsLocked(userId);
2838            user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
2839
2840            if (isPackageInstalled(packageName, userId)) {
2841                user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
2842            }
2843        }
2844        verifyStates();
2845    }
2846
2847    private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
2848        if (DEBUG) {
2849            Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
2850                    packageUserId));
2851        }
2852        cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false);
2853
2854        verifyStates();
2855    }
2856
2857    private void handlePackageDataCleared(String packageName, int packageUserId) {
2858        if (DEBUG) {
2859            Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
2860                    packageUserId));
2861        }
2862        cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true);
2863
2864        verifyStates();
2865    }
2866
2867    private void handlePackageChanged(String packageName, int packageUserId) {
2868        if (!isPackageInstalled(packageName, packageUserId)) {
2869            // Probably disabled, which is the same thing as uninstalled.
2870            handlePackageRemoved(packageName, packageUserId);
2871            return;
2872        }
2873        if (DEBUG) {
2874            Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName,
2875                    packageUserId));
2876        }
2877
2878        // Activities may be disabled or enabled.  Just rescan the package.
2879        synchronized (mLock) {
2880            final ShortcutUser user = getUserShortcutsLocked(packageUserId);
2881
2882            user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
2883        }
2884
2885        verifyStates();
2886    }
2887
2888    // === PackageManager interaction ===
2889
2890    /**
2891     * Returns {@link PackageInfo} unless it's uninstalled or disabled.
2892     */
2893    @Nullable
2894    final PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
2895        return getPackageInfo(packageName, userId, true);
2896    }
2897
2898    /**
2899     * Returns {@link PackageInfo} unless it's uninstalled or disabled.
2900     */
2901    @Nullable
2902    final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
2903        return getPackageInfo(packageName, userId, false);
2904    }
2905
2906    int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
2907        final long token = injectClearCallingIdentity();
2908        try {
2909            return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS, userId);
2910        } catch (RemoteException e) {
2911            // Shouldn't happen.
2912            Slog.wtf(TAG, "RemoteException", e);
2913            return -1;
2914        } finally {
2915            injectRestoreCallingIdentity(token);
2916        }
2917    }
2918
2919    /**
2920     * Returns {@link PackageInfo} unless it's uninstalled or disabled.
2921     */
2922    @Nullable
2923    @VisibleForTesting
2924    final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId,
2925            boolean getSignatures) {
2926        return isInstalledOrNull(injectPackageInfoWithUninstalled(
2927                packageName, userId, getSignatures));
2928    }
2929
2930    /**
2931     * Do not use directly; this returns uninstalled packages too.
2932     */
2933    @Nullable
2934    @VisibleForTesting
2935    PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId,
2936            boolean getSignatures) {
2937        final long start = injectElapsedRealtime();
2938        final long token = injectClearCallingIdentity();
2939        try {
2940            return mIPackageManager.getPackageInfo(
2941                    packageName, PACKAGE_MATCH_FLAGS
2942                            | (getSignatures ? PackageManager.GET_SIGNATURES : 0), userId);
2943        } catch (RemoteException e) {
2944            // Shouldn't happen.
2945            Slog.wtf(TAG, "RemoteException", e);
2946            return null;
2947        } finally {
2948            injectRestoreCallingIdentity(token);
2949
2950            logDurationStat(
2951                    (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
2952                    start);
2953        }
2954    }
2955
2956    /**
2957     * Returns {@link ApplicationInfo} unless it's uninstalled or disabled.
2958     */
2959    @Nullable
2960    @VisibleForTesting
2961    final ApplicationInfo getApplicationInfo(String packageName, @UserIdInt int userId) {
2962        return isInstalledOrNull(injectApplicationInfoWithUninstalled(packageName, userId));
2963    }
2964
2965    /**
2966     * Do not use directly; this returns uninstalled packages too.
2967     */
2968    @Nullable
2969    @VisibleForTesting
2970    ApplicationInfo injectApplicationInfoWithUninstalled(
2971            String packageName, @UserIdInt int userId) {
2972        final long start = injectElapsedRealtime();
2973        final long token = injectClearCallingIdentity();
2974        try {
2975            return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
2976        } catch (RemoteException e) {
2977            // Shouldn't happen.
2978            Slog.wtf(TAG, "RemoteException", e);
2979            return null;
2980        } finally {
2981            injectRestoreCallingIdentity(token);
2982
2983            logDurationStat(Stats.GET_APPLICATION_INFO, start);
2984        }
2985    }
2986
2987    /**
2988     * Returns {@link ActivityInfo} with its metadata unless it's uninstalled or disabled.
2989     */
2990    @Nullable
2991    final ActivityInfo getActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) {
2992        return isInstalledOrNull(injectGetActivityInfoWithMetadataWithUninstalled(
2993                activity, userId));
2994    }
2995
2996    /**
2997     * Do not use directly; this returns uninstalled packages too.
2998     */
2999    @Nullable
3000    @VisibleForTesting
3001    ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled(
3002            ComponentName activity, @UserIdInt int userId) {
3003        final long start = injectElapsedRealtime();
3004        final long token = injectClearCallingIdentity();
3005        try {
3006            return mIPackageManager.getActivityInfo(activity,
3007                    (PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA), userId);
3008        } catch (RemoteException e) {
3009            // Shouldn't happen.
3010            Slog.wtf(TAG, "RemoteException", e);
3011            return null;
3012        } finally {
3013            injectRestoreCallingIdentity(token);
3014
3015            logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start);
3016        }
3017    }
3018
3019    /**
3020     * Return all installed and enabled packages.
3021     */
3022    @NonNull
3023    @VisibleForTesting
3024    final List<PackageInfo> getInstalledPackages(@UserIdInt int userId) {
3025        final long start = injectElapsedRealtime();
3026        final long token = injectClearCallingIdentity();
3027        try {
3028            final List<PackageInfo> all = injectGetPackagesWithUninstalled(userId);
3029
3030            all.removeIf(PACKAGE_NOT_INSTALLED);
3031
3032            return all;
3033        } catch (RemoteException e) {
3034            // Shouldn't happen.
3035            Slog.wtf(TAG, "RemoteException", e);
3036            return null;
3037        } finally {
3038            injectRestoreCallingIdentity(token);
3039
3040            logDurationStat(Stats.GET_INSTALLED_PACKAGES, start);
3041        }
3042    }
3043
3044    /**
3045     * Do not use directly; this returns uninstalled packages too.
3046     */
3047    @NonNull
3048    @VisibleForTesting
3049    List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId)
3050            throws RemoteException {
3051        final ParceledListSlice<PackageInfo> parceledList =
3052                mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId);
3053        if (parceledList == null) {
3054            return Collections.emptyList();
3055        }
3056        return parceledList.getList();
3057    }
3058
3059    private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta,
3060            Consumer<ApplicationInfo> callback) {
3061        if (DEBUG) {
3062            Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime
3063                    + " afterOta=" + afterOta);
3064        }
3065        final List<PackageInfo> list = getInstalledPackages(userId);
3066        for (int i = list.size() - 1; i >= 0; i--) {
3067            final PackageInfo pi = list.get(i);
3068
3069            // If the package has been updated since the last scan time, then scan it.
3070            // Also if it's right after an OTA, always re-scan all apps anyway, since the
3071            // shortcut parser might have changed.
3072            if (afterOta || (pi.lastUpdateTime >= lastScanTime)) {
3073                if (DEBUG) {
3074                    Slog.d(TAG, "Found updated package " + pi.packageName
3075                            + " updateTime=" + pi.lastUpdateTime);
3076                }
3077                callback.accept(pi.applicationInfo);
3078            }
3079        }
3080    }
3081
3082    private boolean isApplicationFlagSet(@NonNull String packageName, int userId, int flags) {
3083        final ApplicationInfo ai = injectApplicationInfoWithUninstalled(packageName, userId);
3084        return (ai != null) && ((ai.flags & flags) == flags);
3085    }
3086
3087    private static boolean isInstalled(@Nullable ApplicationInfo ai) {
3088        return (ai != null) && ai.enabled && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
3089    }
3090
3091    private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) {
3092        return (ai != null) && ai.isInstantApp();
3093    }
3094
3095    private static boolean isInstalled(@Nullable PackageInfo pi) {
3096        return (pi != null) && isInstalled(pi.applicationInfo);
3097    }
3098
3099    private static boolean isInstalled(@Nullable ActivityInfo ai) {
3100        return (ai != null) && isInstalled(ai.applicationInfo);
3101    }
3102
3103    private static ApplicationInfo isInstalledOrNull(ApplicationInfo ai) {
3104        return isInstalled(ai) ? ai : null;
3105    }
3106
3107    private static PackageInfo isInstalledOrNull(PackageInfo pi) {
3108        return isInstalled(pi) ? pi : null;
3109    }
3110
3111    private static ActivityInfo isInstalledOrNull(ActivityInfo ai) {
3112        return isInstalled(ai) ? ai : null;
3113    }
3114
3115    boolean isPackageInstalled(String packageName, int userId) {
3116        return getApplicationInfo(packageName, userId) != null;
3117    }
3118
3119    boolean isEphemeralApp(String packageName, int userId) {
3120        return isEphemeralApp(getApplicationInfo(packageName, userId));
3121    }
3122
3123    @Nullable
3124    XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
3125        return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
3126    }
3127
3128    @Nullable
3129    Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) {
3130        final long start = injectElapsedRealtime();
3131        final long token = injectClearCallingIdentity();
3132        try {
3133            return mContext.getPackageManager().getResourcesForApplicationAsUser(
3134                    packageName, userId);
3135        } catch (NameNotFoundException e) {
3136            Slog.e(TAG, "Resources for package " + packageName + " not found");
3137            return null;
3138        } finally {
3139            injectRestoreCallingIdentity(token);
3140
3141            logDurationStat(Stats.GET_APPLICATION_RESOURCES, start);
3142        }
3143    }
3144
3145    private Intent getMainActivityIntent() {
3146        final Intent intent = new Intent(Intent.ACTION_MAIN);
3147        intent.addCategory(LAUNCHER_INTENT_CATEGORY);
3148        return intent;
3149    }
3150
3151    /**
3152     * Same as queryIntentActivitiesAsUser, except it makes sure the package is installed,
3153     * and only returns exported activities.
3154     */
3155    @NonNull
3156    @VisibleForTesting
3157    List<ResolveInfo> queryActivities(@NonNull Intent baseIntent,
3158            @NonNull String packageName, @Nullable ComponentName activity, int userId) {
3159
3160        baseIntent.setPackage(Preconditions.checkNotNull(packageName));
3161        if (activity != null) {
3162            baseIntent.setComponent(activity);
3163        }
3164        return queryActivities(baseIntent, userId, /* exportedOnly =*/ true);
3165    }
3166
3167    @NonNull
3168    List<ResolveInfo> queryActivities(@NonNull Intent intent, int userId,
3169            boolean exportedOnly) {
3170        final List<ResolveInfo> resolved;
3171        final long token = injectClearCallingIdentity();
3172        try {
3173            resolved =
3174                    mContext.getPackageManager().queryIntentActivitiesAsUser(
3175                            intent, PACKAGE_MATCH_FLAGS, userId);
3176        } finally {
3177            injectRestoreCallingIdentity(token);
3178        }
3179        if (resolved == null || resolved.size() == 0) {
3180            return EMPTY_RESOLVE_INFO;
3181        }
3182        // Make sure the package is installed.
3183        if (!isInstalled(resolved.get(0).activityInfo)) {
3184            return EMPTY_RESOLVE_INFO;
3185        }
3186        if (exportedOnly) {
3187            resolved.removeIf(ACTIVITY_NOT_EXPORTED);
3188        }
3189        return resolved;
3190    }
3191
3192    /**
3193     * Return the main activity that is enabled and exported.  If multiple activities are found,
3194     * return the first one.
3195     */
3196    @Nullable
3197    ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) {
3198        final long start = injectElapsedRealtime();
3199        try {
3200            final List<ResolveInfo> resolved =
3201                    queryActivities(getMainActivityIntent(), packageName, null, userId);
3202            return resolved.size() == 0 ? null : resolved.get(0).activityInfo.getComponentName();
3203        } finally {
3204            logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start);
3205        }
3206    }
3207
3208    /**
3209     * Return whether an activity is enabled, exported and main.
3210     */
3211    boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
3212        final long start = injectElapsedRealtime();
3213        try {
3214            if (activity == null) {
3215                wtf("null activity detected");
3216                return false;
3217            }
3218            if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) {
3219                return true;
3220            }
3221            final List<ResolveInfo> resolved = queryActivities(
3222                    getMainActivityIntent(), activity.getPackageName(), activity, userId);
3223            return resolved.size() > 0;
3224        } finally {
3225            logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
3226        }
3227    }
3228
3229    /**
3230     * Create a dummy "main activity" component name which is used to create a dynamic shortcut
3231     * with no main activity temporarily.
3232     */
3233    @NonNull
3234    ComponentName getDummyMainActivity(@NonNull String packageName) {
3235        return new ComponentName(packageName, DUMMY_MAIN_ACTIVITY);
3236    }
3237
3238    boolean isDummyMainActivity(@Nullable ComponentName name) {
3239        return name != null && DUMMY_MAIN_ACTIVITY.equals(name.getClassName());
3240    }
3241
3242    /**
3243     * Return all the enabled, exported and main activities from a package.
3244     */
3245    @NonNull
3246    List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) {
3247        final long start = injectElapsedRealtime();
3248        try {
3249            return queryActivities(getMainActivityIntent(), packageName, null, userId);
3250        } finally {
3251            logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
3252        }
3253    }
3254
3255    /**
3256     * Return whether an activity is enabled and exported.
3257     */
3258    @VisibleForTesting
3259    boolean injectIsActivityEnabledAndExported(
3260            @NonNull ComponentName activity, @UserIdInt int userId) {
3261        final long start = injectElapsedRealtime();
3262        try {
3263            return queryActivities(new Intent(), activity.getPackageName(), activity, userId)
3264                    .size() > 0;
3265        } finally {
3266            logDurationStat(Stats.IS_ACTIVITY_ENABLED, start);
3267        }
3268    }
3269
3270    /**
3271     * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or
3272     * {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on
3273     * the requestType.
3274     */
3275    @Nullable
3276    ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
3277            int launcherUserId, int requestType) {
3278        Preconditions.checkNotNull(launcherPackageName);
3279        String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
3280                LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
3281                LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
3282
3283        final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName);
3284        final List<ResolveInfo> candidates = queryActivities(
3285                confirmIntent, launcherUserId, /* exportedOnly =*/ false);
3286        for (ResolveInfo ri : candidates) {
3287            return ri.activityInfo.getComponentName();
3288        }
3289        return null;
3290    }
3291
3292    boolean injectIsSafeModeEnabled() {
3293        final long token = injectClearCallingIdentity();
3294        try {
3295            return IWindowManager.Stub
3296                    .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE))
3297                    .isSafeModeEnabled();
3298        } catch (RemoteException e) {
3299            return false; // Shouldn't happen though.
3300        } finally {
3301            injectRestoreCallingIdentity(token);
3302        }
3303    }
3304
3305    /**
3306     * If {@code userId} is of a managed profile, return the parent user ID.  Otherwise return
3307     * itself.
3308     */
3309    int getParentOrSelfUserId(int userId) {
3310        final long token = injectClearCallingIdentity();
3311        try {
3312            final UserInfo parent = mUserManager.getProfileParent(userId);
3313            return (parent != null) ? parent.id : userId;
3314        } finally {
3315            injectRestoreCallingIdentity(token);
3316        }
3317    }
3318
3319    void injectSendIntentSender(IntentSender intentSender, Intent extras) {
3320        if (intentSender == null) {
3321            return;
3322        }
3323        try {
3324            intentSender.sendIntent(mContext, /* code= */ 0, extras,
3325                    /* onFinished=*/ null, /* handler= */ null);
3326        } catch (SendIntentException e) {
3327            Slog.w(TAG, "sendIntent failed().", e);
3328        }
3329    }
3330
3331    // === Backup & restore ===
3332
3333    boolean shouldBackupApp(String packageName, int userId) {
3334        return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
3335    }
3336
3337    boolean shouldBackupApp(PackageInfo pi) {
3338        return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
3339    }
3340
3341    @Override
3342    public byte[] getBackupPayload(@UserIdInt int userId) {
3343        enforceSystem();
3344        if (DEBUG) {
3345            Slog.d(TAG, "Backing up user " + userId);
3346        }
3347        synchronized (mLock) {
3348            if (!isUserUnlockedL(userId)) {
3349                wtf("Can't backup: user " + userId + " is locked or not running");
3350                return null;
3351            }
3352
3353            final ShortcutUser user = getUserShortcutsLocked(userId);
3354            if (user == null) {
3355                wtf("Can't backup: user not found: id=" + userId);
3356                return null;
3357            }
3358
3359            // Update the signatures for all packages.
3360            user.forAllPackageItems(spi -> spi.refreshPackageSignatureAndSave());
3361
3362            // Set the version code for the launchers.
3363            // We shouldn't do this for publisher packages, because we don't want to update the
3364            // version code without rescanning the manifest.
3365            user.forAllLaunchers(launcher -> launcher.ensureVersionInfo());
3366
3367            // Save to the filesystem.
3368            scheduleSaveUser(userId);
3369            saveDirtyInfo();
3370
3371            // Note, in case of backup, we don't have to wait on bitmap saving, because we don't
3372            // back up bitmaps anyway.
3373
3374            // Then create the backup payload.
3375            final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
3376            try {
3377                saveUserInternalLocked(userId, os, /* forBackup */ true);
3378            } catch (XmlPullParserException | IOException e) {
3379                // Shouldn't happen.
3380                Slog.w(TAG, "Backup failed.", e);
3381                return null;
3382            }
3383            return os.toByteArray();
3384        }
3385    }
3386
3387    @Override
3388    public void applyRestore(byte[] payload, @UserIdInt int userId) {
3389        enforceSystem();
3390        if (DEBUG) {
3391            Slog.d(TAG, "Restoring user " + userId);
3392        }
3393        synchronized (mLock) {
3394            if (!isUserUnlockedL(userId)) {
3395                wtf("Can't restore: user " + userId + " is locked or not running");
3396                return;
3397            }
3398            // Actually do restore.
3399            final ShortcutUser restored;
3400            final ByteArrayInputStream is = new ByteArrayInputStream(payload);
3401            try {
3402                restored = loadUserInternal(userId, is, /* fromBackup */ true);
3403            } catch (XmlPullParserException | IOException | InvalidFileFormatException e) {
3404                Slog.w(TAG, "Restoration failed.", e);
3405                return;
3406            }
3407            getUserShortcutsLocked(userId).mergeRestoredFile(restored);
3408
3409            // Rescan all packages to re-publish manifest shortcuts and do other checks.
3410            rescanUpdatedPackagesLocked(userId,
3411                    0 // lastScanTime = 0; rescan all packages.
3412                    );
3413
3414            saveUserLocked(userId);
3415        }
3416    }
3417
3418    // === Dump ===
3419
3420    @Override
3421    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3422        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
3423        dumpNoCheck(fd, pw, args);
3424    }
3425
3426    @VisibleForTesting
3427    void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) {
3428        boolean checkin = false;
3429        boolean clear = false;
3430        if (args != null) {
3431            for (String arg : args) {
3432                if ("-c".equals(arg)) {
3433                    checkin = true;
3434                } else if ("--checkin".equals(arg)) {
3435                    checkin = true;
3436                    clear = true;
3437                }
3438            }
3439        }
3440
3441        if (checkin) {
3442            dumpCheckin(pw, clear);
3443        } else {
3444            dumpInner(pw);
3445        }
3446    }
3447
3448    private void dumpInner(PrintWriter pw) {
3449        synchronized (mLock) {
3450            final long now = injectCurrentTimeMillis();
3451            pw.print("Now: [");
3452            pw.print(now);
3453            pw.print("] ");
3454            pw.print(formatTime(now));
3455
3456            pw.print("  Raw last reset: [");
3457            pw.print(mRawLastResetTime);
3458            pw.print("] ");
3459            pw.print(formatTime(mRawLastResetTime));
3460
3461            final long last = getLastResetTimeLocked();
3462            pw.print("  Last reset: [");
3463            pw.print(last);
3464            pw.print("] ");
3465            pw.print(formatTime(last));
3466
3467            final long next = getNextResetTimeLocked();
3468            pw.print("  Next reset: [");
3469            pw.print(next);
3470            pw.print("] ");
3471            pw.print(formatTime(next));
3472
3473            pw.print("  Config:");
3474            pw.print("    Max icon dim: ");
3475            pw.println(mMaxIconDimension);
3476            pw.print("    Icon format: ");
3477            pw.println(mIconPersistFormat);
3478            pw.print("    Icon quality: ");
3479            pw.println(mIconPersistQuality);
3480            pw.print("    saveDelayMillis: ");
3481            pw.println(mSaveDelayMillis);
3482            pw.print("    resetInterval: ");
3483            pw.println(mResetInterval);
3484            pw.print("    maxUpdatesPerInterval: ");
3485            pw.println(mMaxUpdatesPerInterval);
3486            pw.print("    maxShortcutsPerActivity: ");
3487            pw.println(mMaxShortcuts);
3488            pw.println();
3489
3490            pw.println("  Stats:");
3491            synchronized (mStatLock) {
3492                for (int i = 0; i < Stats.COUNT; i++) {
3493                    dumpStatLS(pw, "    ", i);
3494                }
3495            }
3496
3497            pw.println();
3498            pw.print("  #Failures: ");
3499            pw.println(mWtfCount);
3500
3501            if (mLastWtfStacktrace != null) {
3502                pw.print("  Last failure stack trace: ");
3503                pw.println(Log.getStackTraceString(mLastWtfStacktrace));
3504            }
3505
3506            pw.println();
3507            mShortcutBitmapSaver.dumpLocked(pw, "  ");
3508
3509            for (int i = 0; i < mUsers.size(); i++) {
3510                pw.println();
3511                mUsers.valueAt(i).dump(pw, "  ");
3512            }
3513
3514            pw.println();
3515            pw.println("  UID state:");
3516
3517            for (int i = 0; i < mUidState.size(); i++) {
3518                final int uid = mUidState.keyAt(i);
3519                final int state = mUidState.valueAt(i);
3520                pw.print("    UID=");
3521                pw.print(uid);
3522                pw.print(" state=");
3523                pw.print(state);
3524                if (isProcessStateForeground(state)) {
3525                    pw.print("  [FG]");
3526                }
3527                pw.print("  last FG=");
3528                pw.print(mUidLastForegroundElapsedTime.get(uid));
3529                pw.println();
3530            }
3531        }
3532    }
3533
3534    static String formatTime(long time) {
3535        Time tobj = new Time();
3536        tobj.set(time);
3537        return tobj.format("%Y-%m-%d %H:%M:%S");
3538    }
3539
3540    private void dumpStatLS(PrintWriter pw, String prefix, int statId) {
3541        pw.print(prefix);
3542        final int count = mCountStats[statId];
3543        final long dur = mDurationStats[statId];
3544        pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
3545                STAT_LABELS[statId], count, dur,
3546                (count == 0 ? 0 : ((double) dur) / count)));
3547    }
3548
3549    /**
3550     * Dumpsys for checkin.
3551     *
3552     * @param clear if true, clear the history information.  Some other system services have this
3553     * behavior but shortcut service doesn't for now.
3554     */
3555    private  void dumpCheckin(PrintWriter pw, boolean clear) {
3556        synchronized (mLock) {
3557            try {
3558                final JSONArray users = new JSONArray();
3559
3560                for (int i = 0; i < mUsers.size(); i++) {
3561                    users.put(mUsers.valueAt(i).dumpCheckin(clear));
3562                }
3563
3564                final JSONObject result = new JSONObject();
3565
3566                result.put(KEY_SHORTCUT, users);
3567                result.put(KEY_LOW_RAM, injectIsLowRamDevice());
3568                result.put(KEY_ICON_SIZE, mMaxIconDimension);
3569
3570                pw.println(result.toString(1));
3571            } catch (JSONException e) {
3572                Slog.e(TAG, "Unable to write in json", e);
3573            }
3574        }
3575    }
3576
3577    // === Shell support ===
3578
3579    @Override
3580    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
3581            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
3582
3583        enforceShell();
3584
3585        final long token = injectClearCallingIdentity();
3586        try {
3587            final int status = (new MyShellCommand()).exec(this, in, out, err, args, callback,
3588                    resultReceiver);
3589            resultReceiver.send(status, null);
3590        } finally {
3591            injectRestoreCallingIdentity(token);
3592        }
3593    }
3594
3595    static class CommandException extends Exception {
3596        public CommandException(String message) {
3597            super(message);
3598        }
3599    }
3600
3601    /**
3602     * Handle "adb shell cmd".
3603     */
3604    private class MyShellCommand extends ShellCommand {
3605
3606        private int mUserId = UserHandle.USER_SYSTEM;
3607
3608        private void parseOptionsLocked(boolean takeUser)
3609                throws CommandException {
3610            String opt;
3611            while ((opt = getNextOption()) != null) {
3612                switch (opt) {
3613                    case "--user":
3614                        if (takeUser) {
3615                            mUserId = UserHandle.parseUserArg(getNextArgRequired());
3616                            if (!isUserUnlockedL(mUserId)) {
3617                                throw new CommandException(
3618                                        "User " + mUserId + " is not running or locked");
3619                            }
3620                            break;
3621                        }
3622                        // fallthrough
3623                    default:
3624                        throw new CommandException("Unknown option: " + opt);
3625                }
3626            }
3627        }
3628
3629        @Override
3630        public int onCommand(String cmd) {
3631            if (cmd == null) {
3632                return handleDefaultCommands(cmd);
3633            }
3634            final PrintWriter pw = getOutPrintWriter();
3635            try {
3636                switch (cmd) {
3637                    case "reset-throttling":
3638                        handleResetThrottling();
3639                        break;
3640                    case "reset-all-throttling":
3641                        handleResetAllThrottling();
3642                        break;
3643                    case "override-config":
3644                        handleOverrideConfig();
3645                        break;
3646                    case "reset-config":
3647                        handleResetConfig();
3648                        break;
3649                    case "clear-default-launcher":
3650                        handleClearDefaultLauncher();
3651                        break;
3652                    case "get-default-launcher":
3653                        handleGetDefaultLauncher();
3654                        break;
3655                    case "unload-user":
3656                        handleUnloadUser();
3657                        break;
3658                    case "clear-shortcuts":
3659                        handleClearShortcuts();
3660                        break;
3661                    case "verify-states": // hidden command to verify various internal states.
3662                        handleVerifyStates();
3663                        break;
3664                    default:
3665                        return handleDefaultCommands(cmd);
3666                }
3667            } catch (CommandException e) {
3668                pw.println("Error: " + e.getMessage());
3669                return 1;
3670            }
3671            pw.println("Success");
3672            return 0;
3673        }
3674
3675        @Override
3676        public void onHelp() {
3677            final PrintWriter pw = getOutPrintWriter();
3678            pw.println("Usage: cmd shortcut COMMAND [options ...]");
3679            pw.println();
3680            pw.println("cmd shortcut reset-throttling [--user USER_ID]");
3681            pw.println("    Reset throttling for all packages and users");
3682            pw.println();
3683            pw.println("cmd shortcut reset-all-throttling");
3684            pw.println("    Reset the throttling state for all users");
3685            pw.println();
3686            pw.println("cmd shortcut override-config CONFIG");
3687            pw.println("    Override the configuration for testing (will last until reboot)");
3688            pw.println();
3689            pw.println("cmd shortcut reset-config");
3690            pw.println("    Reset the configuration set with \"update-config\"");
3691            pw.println();
3692            pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
3693            pw.println("    Clear the cached default launcher");
3694            pw.println();
3695            pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
3696            pw.println("    Show the default launcher");
3697            pw.println();
3698            pw.println("cmd shortcut unload-user [--user USER_ID]");
3699            pw.println("    Unload a user from the memory");
3700            pw.println("    (This should not affect any observable behavior)");
3701            pw.println();
3702            pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE");
3703            pw.println("    Remove all shortcuts from a package, including pinned shortcuts");
3704            pw.println();
3705        }
3706
3707        private void handleResetThrottling() throws CommandException {
3708            synchronized (mLock) {
3709                parseOptionsLocked(/* takeUser =*/ true);
3710
3711                Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId);
3712
3713                resetThrottlingInner(mUserId);
3714            }
3715        }
3716
3717        private void handleResetAllThrottling() {
3718            Slog.i(TAG, "cmd: handleResetAllThrottling");
3719
3720            resetAllThrottlingInner();
3721        }
3722
3723        private void handleOverrideConfig() throws CommandException {
3724            final String config = getNextArgRequired();
3725
3726            Slog.i(TAG, "cmd: handleOverrideConfig: " + config);
3727
3728            synchronized (mLock) {
3729                if (!updateConfigurationLocked(config)) {
3730                    throw new CommandException("override-config failed.  See logcat for details.");
3731                }
3732            }
3733        }
3734
3735        private void handleResetConfig() {
3736            Slog.i(TAG, "cmd: handleResetConfig");
3737
3738            synchronized (mLock) {
3739                loadConfigurationLocked();
3740            }
3741        }
3742
3743        private void clearLauncher() {
3744            synchronized (mLock) {
3745                getUserShortcutsLocked(mUserId).forceClearLauncher();
3746            }
3747        }
3748
3749        private void showLauncher() {
3750            synchronized (mLock) {
3751                // This ensures to set the cached launcher.  Package name doesn't matter.
3752                hasShortcutHostPermissionInner("-", mUserId);
3753
3754                getOutPrintWriter().println("Launcher: "
3755                        + getUserShortcutsLocked(mUserId).getLastKnownLauncher());
3756            }
3757        }
3758
3759        private void handleClearDefaultLauncher() throws CommandException {
3760            synchronized (mLock) {
3761                parseOptionsLocked(/* takeUser =*/ true);
3762
3763                clearLauncher();
3764            }
3765        }
3766
3767        private void handleGetDefaultLauncher() throws CommandException {
3768            synchronized (mLock) {
3769                parseOptionsLocked(/* takeUser =*/ true);
3770
3771                clearLauncher();
3772                showLauncher();
3773            }
3774        }
3775
3776        private void handleUnloadUser() throws CommandException {
3777            synchronized (mLock) {
3778                parseOptionsLocked(/* takeUser =*/ true);
3779
3780                Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId);
3781
3782                ShortcutService.this.handleStopUser(mUserId);
3783            }
3784        }
3785
3786        private void handleClearShortcuts() throws CommandException {
3787            synchronized (mLock) {
3788                parseOptionsLocked(/* takeUser =*/ true);
3789                final String packageName = getNextArgRequired();
3790
3791                Slog.i(TAG, "cmd: handleClearShortcuts: user" + mUserId + ", " + packageName);
3792
3793                ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId,
3794                        /* appStillExists = */ true);
3795            }
3796        }
3797
3798        private void handleVerifyStates() throws CommandException {
3799            try {
3800                verifyStatesForce(); // This will throw when there's an issue.
3801            } catch (Throwable th) {
3802                throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th));
3803            }
3804        }
3805    }
3806
3807    // === Unit test support ===
3808
3809    // Injection point.
3810    @VisibleForTesting
3811    long injectCurrentTimeMillis() {
3812        return System.currentTimeMillis();
3813    }
3814
3815    @VisibleForTesting
3816    long injectElapsedRealtime() {
3817        return SystemClock.elapsedRealtime();
3818    }
3819
3820    @VisibleForTesting
3821    long injectUptimeMillis() {
3822        return SystemClock.uptimeMillis();
3823    }
3824
3825    // Injection point.
3826    @VisibleForTesting
3827    int injectBinderCallingUid() {
3828        return getCallingUid();
3829    }
3830
3831    private int getCallingUserId() {
3832        return UserHandle.getUserId(injectBinderCallingUid());
3833    }
3834
3835    // Injection point.
3836    @VisibleForTesting
3837    long injectClearCallingIdentity() {
3838        return Binder.clearCallingIdentity();
3839    }
3840
3841    // Injection point.
3842    @VisibleForTesting
3843    void injectRestoreCallingIdentity(long token) {
3844        Binder.restoreCallingIdentity(token);
3845    }
3846
3847    // Injection point.
3848    @VisibleForTesting
3849    String injectBuildFingerprint() {
3850        return Build.FINGERPRINT;
3851    }
3852
3853    final void wtf(String message) {
3854        wtf(message, /* exception= */ null);
3855    }
3856
3857    // Injection point.
3858    void wtf(String message, Throwable e) {
3859        if (e == null) {
3860            e = new RuntimeException("Stacktrace");
3861        }
3862        synchronized (mLock) {
3863            mWtfCount++;
3864            mLastWtfStacktrace = new Exception("Last failure was logged here:");
3865        }
3866        Slog.wtf(TAG, message, e);
3867    }
3868
3869    @VisibleForTesting
3870    File injectSystemDataPath() {
3871        return Environment.getDataSystemDirectory();
3872    }
3873
3874    @VisibleForTesting
3875    File injectUserDataPath(@UserIdInt int userId) {
3876        return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
3877    }
3878
3879    @VisibleForTesting
3880    boolean injectIsLowRamDevice() {
3881        return ActivityManager.isLowRamDeviceStatic();
3882    }
3883
3884    @VisibleForTesting
3885    void injectRegisterUidObserver(IUidObserver observer, int which) {
3886        try {
3887            ActivityManager.getService().registerUidObserver(observer, which,
3888                    ActivityManager.PROCESS_STATE_UNKNOWN, null);
3889        } catch (RemoteException shouldntHappen) {
3890        }
3891    }
3892
3893    File getUserBitmapFilePath(@UserIdInt int userId) {
3894        return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
3895    }
3896
3897    @VisibleForTesting
3898    SparseArray<ShortcutUser> getShortcutsForTest() {
3899        return mUsers;
3900    }
3901
3902    @VisibleForTesting
3903    int getMaxShortcutsForTest() {
3904        return mMaxShortcuts;
3905    }
3906
3907    @VisibleForTesting
3908    int getMaxUpdatesPerIntervalForTest() {
3909        return mMaxUpdatesPerInterval;
3910    }
3911
3912    @VisibleForTesting
3913    long getResetIntervalForTest() {
3914        return mResetInterval;
3915    }
3916
3917    @VisibleForTesting
3918    int getMaxIconDimensionForTest() {
3919        return mMaxIconDimension;
3920    }
3921
3922    @VisibleForTesting
3923    CompressFormat getIconPersistFormatForTest() {
3924        return mIconPersistFormat;
3925    }
3926
3927    @VisibleForTesting
3928    int getIconPersistQualityForTest() {
3929        return mIconPersistQuality;
3930    }
3931
3932    @VisibleForTesting
3933    ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
3934        synchronized (mLock) {
3935            final ShortcutUser user = mUsers.get(userId);
3936            if (user == null) return null;
3937
3938            return user.getAllPackagesForTest().get(packageName);
3939        }
3940    }
3941
3942    @VisibleForTesting
3943    ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
3944        synchronized (mLock) {
3945            final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
3946            if (pkg == null) return null;
3947
3948            return pkg.findShortcutById(shortcutId);
3949        }
3950    }
3951
3952    @VisibleForTesting
3953    ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
3954        synchronized (mLock) {
3955            final ShortcutUser user = mUsers.get(userId);
3956            if (user == null) return null;
3957
3958            return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName));
3959        }
3960    }
3961
3962    @VisibleForTesting
3963    ShortcutRequestPinProcessor getShortcutRequestPinProcessorForTest() {
3964        return mShortcutRequestPinProcessor;
3965    }
3966
3967    /**
3968     * Control whether {@link #verifyStates} should be performed.  We always perform it during unit
3969     * tests.
3970     */
3971    @VisibleForTesting
3972    boolean injectShouldPerformVerification() {
3973        return DEBUG;
3974    }
3975
3976    /**
3977     * Check various internal states and throws if there's any inconsistency.
3978     * This is normally only enabled during unit tests.
3979     */
3980    final void verifyStates() {
3981        if (injectShouldPerformVerification()) {
3982            verifyStatesInner();
3983        }
3984    }
3985
3986    private final void verifyStatesForce() {
3987        verifyStatesInner();
3988    }
3989
3990    private void verifyStatesInner() {
3991        synchronized (mLock) {
3992            forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
3993        }
3994    }
3995
3996    @VisibleForTesting
3997    void waitForBitmapSavesForTest() {
3998        synchronized (mLock) {
3999            mShortcutBitmapSaver.waitForAllSavesLocked();
4000        }
4001    }
4002}
4003