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