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