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