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