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