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