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