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