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