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 android.annotation.IntDef;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.app.ActivityManager;
23 import android.app.ActivityManagerInternal;
24 import android.app.AppGlobals;
25 import android.app.IUidObserver;
26 import android.app.usage.UsageStatsManagerInternal;
27 import android.appwidget.AppWidgetProviderInfo;
28 import android.content.BroadcastReceiver;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.IntentSender;
34 import android.content.IntentSender.SendIntentException;
35 import android.content.pm.ActivityInfo;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.IPackageManager;
38 import android.content.pm.IShortcutService;
39 import android.content.pm.LauncherApps;
40 import android.content.pm.LauncherApps.ShortcutQuery;
41 import android.content.pm.PackageInfo;
42 import android.content.pm.PackageManager;
43 import android.content.pm.PackageManager.NameNotFoundException;
44 import android.content.pm.PackageManagerInternal;
45 import android.content.pm.ParceledListSlice;
46 import android.content.pm.ResolveInfo;
47 import android.content.pm.ShortcutInfo;
48 import android.content.pm.ShortcutServiceInternal;
49 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
50 import android.content.pm.UserInfo;
51 import android.content.res.Resources;
52 import android.content.res.XmlResourceParser;
53 import android.graphics.Bitmap;
54 import android.graphics.Bitmap.CompressFormat;
55 import android.graphics.Canvas;
56 import android.graphics.RectF;
57 import android.graphics.drawable.AdaptiveIconDrawable;
58 import android.graphics.drawable.Icon;
59 import android.net.Uri;
60 import android.os.Binder;
61 import android.os.Build;
62 import android.os.Bundle;
63 import android.os.Environment;
64 import android.os.FileUtils;
65 import android.os.Handler;
66 import android.os.LocaleList;
67 import android.os.Looper;
68 import android.os.ParcelFileDescriptor;
69 import android.os.PersistableBundle;
70 import android.os.Process;
71 import android.os.RemoteException;
72 import android.os.ResultReceiver;
73 import android.os.SELinux;
74 import android.os.ServiceManager;
75 import android.os.ShellCallback;
76 import android.os.ShellCommand;
77 import android.os.SystemClock;
78 import android.os.UserHandle;
79 import android.os.UserManager;
80 import android.text.TextUtils;
81 import android.text.format.Time;
82 import android.util.ArraySet;
83 import android.util.AtomicFile;
84 import android.util.KeyValueListParser;
85 import android.util.Log;
86 import android.util.Slog;
87 import android.util.SparseArray;
88 import android.util.SparseBooleanArray;
89 import android.util.SparseIntArray;
90 import android.util.SparseLongArray;
91 import android.util.TypedValue;
92 import android.util.Xml;
93 import android.view.IWindowManager;
94 
95 import com.android.internal.annotations.GuardedBy;
96 import com.android.internal.annotations.VisibleForTesting;
97 import com.android.internal.os.BackgroundThread;
98 import com.android.internal.util.DumpUtils;
99 import com.android.internal.util.FastXmlSerializer;
100 import com.android.internal.util.Preconditions;
101 import com.android.server.LocalServices;
102 import com.android.server.SystemService;
103 import com.android.server.pm.ShortcutUser.PackageWithUser;
104 
105 import libcore.io.IoUtils;
106 
107 import org.json.JSONArray;
108 import org.json.JSONException;
109 import org.json.JSONObject;
110 import org.xmlpull.v1.XmlPullParser;
111 import org.xmlpull.v1.XmlPullParserException;
112 import org.xmlpull.v1.XmlSerializer;
113 
114 import java.io.BufferedInputStream;
115 import java.io.BufferedOutputStream;
116 import java.io.ByteArrayInputStream;
117 import java.io.ByteArrayOutputStream;
118 import java.io.File;
119 import java.io.FileDescriptor;
120 import java.io.FileInputStream;
121 import java.io.FileNotFoundException;
122 import java.io.FileOutputStream;
123 import java.io.IOException;
124 import java.io.InputStream;
125 import java.io.OutputStream;
126 import java.io.PrintWriter;
127 import java.lang.annotation.Retention;
128 import java.lang.annotation.RetentionPolicy;
129 import java.net.URISyntaxException;
130 import java.nio.charset.StandardCharsets;
131 import java.util.ArrayList;
132 import java.util.Collections;
133 import java.util.List;
134 import java.util.concurrent.atomic.AtomicBoolean;
135 import java.util.function.Consumer;
136 import java.util.function.Predicate;
137 
138 /**
139  * TODO:
140  * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
141  *   -> But TypedValue.applyDimension() doesn't differentiate x and y..?
142  *
143  * - Detect when already registered instances are passed to APIs again, which might break
144  * internal bitmap handling.
145  */
146 public class ShortcutService extends IShortcutService.Stub {
147     static final String TAG = "ShortcutService";
148 
149     static final boolean DEBUG = false; // STOPSHIP if true
150     static final boolean DEBUG_LOAD = false; // STOPSHIP if true
151     static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
152 
153     @VisibleForTesting
154     static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
155 
156     @VisibleForTesting
157     static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
158 
159     @VisibleForTesting
160     static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
161 
162     @VisibleForTesting
163     static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
164 
165     @VisibleForTesting
166     static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
167 
168     @VisibleForTesting
169     static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
170 
171     @VisibleForTesting
172     static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
173 
174     @VisibleForTesting
175     static final int DEFAULT_SAVE_DELAY_MS = 3000;
176 
177     @VisibleForTesting
178     static final String FILENAME_BASE_STATE = "shortcut_service.xml";
179 
180     @VisibleForTesting
181     static final String DIRECTORY_PER_USER = "shortcut_service";
182 
183     @VisibleForTesting
184     static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
185 
186     static final String DIRECTORY_BITMAPS = "bitmaps";
187 
188     private static final String TAG_ROOT = "root";
189     private static final String TAG_LAST_RESET_TIME = "last_reset_time";
190 
191     private static final String ATTR_VALUE = "value";
192 
193     private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER;
194 
195     private static final String KEY_SHORTCUT = "shortcut";
196     private static final String KEY_LOW_RAM = "lowRam";
197     private static final String KEY_ICON_SIZE = "iconSize";
198 
199     private static final String DUMMY_MAIN_ACTIVITY = "android.__dummy__";
200 
201     @VisibleForTesting
202     interface ConfigConstants {
203         /**
204          * Key name for the save delay, in milliseconds. (int)
205          */
206         String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
207 
208         /**
209          * Key name for the throttling reset interval, in seconds. (long)
210          */
211         String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
212 
213         /**
214          * Key name for the max number of modifying API calls per app for every interval. (int)
215          */
216         String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval";
217 
218         /**
219          * Key name for the max icon dimensions in DP, for non-low-memory devices.
220          */
221         String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
222 
223         /**
224          * Key name for the max icon dimensions in DP, for low-memory devices.
225          */
226         String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
227 
228         /**
229          * Key name for the max dynamic shortcuts per activity. (int)
230          */
231         String KEY_MAX_SHORTCUTS = "max_shortcuts";
232 
233         /**
234          * Key name for icon compression quality, 0-100.
235          */
236         String KEY_ICON_QUALITY = "icon_quality";
237 
238         /**
239          * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
240          */
241         String KEY_ICON_FORMAT = "icon_format";
242     }
243 
244     final Context mContext;
245 
246     private final Object mLock = new Object();
247 
248     private static List<ResolveInfo> EMPTY_RESOLVE_INFO = new ArrayList<>(0);
249 
250     // Temporarily reverted to anonymous inner class form due to: b/32554459
251     private static Predicate<ResolveInfo> ACTIVITY_NOT_EXPORTED = new Predicate<ResolveInfo>() {
252         public boolean test(ResolveInfo ri) {
253             return !ri.activityInfo.exported;
254         }
255     };
256 
257     // Temporarily reverted to anonymous inner class form due to: b/32554459
258     private static Predicate<PackageInfo> PACKAGE_NOT_INSTALLED = new Predicate<PackageInfo>() {
259         public boolean test(PackageInfo pi) {
260             return !isInstalled(pi);
261         }
262     };
263 
264     private final Handler mHandler;
265 
266     @GuardedBy("mLock")
267     private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
268 
269     @GuardedBy("mLock")
270     private long mRawLastResetTime;
271 
272     /**
273      * User ID -> UserShortcuts
274      */
275     @GuardedBy("mLock")
276     private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
277 
278     /**
279      * Max number of dynamic + manifest shortcuts that each application can have at a time.
280      */
281     private int mMaxShortcuts;
282 
283     /**
284      * Max number of updating API calls that each application can make during the interval.
285      */
286     int mMaxUpdatesPerInterval;
287 
288     /**
289      * Actual throttling-reset interval.  By default it's a day.
290      */
291     private long mResetInterval;
292 
293     /**
294      * Icon max width/height in pixels.
295      */
296     private int mMaxIconDimension;
297 
298     private CompressFormat mIconPersistFormat;
299     private int mIconPersistQuality;
300 
301     private int mSaveDelayMillis;
302 
303     private final IPackageManager mIPackageManager;
304     private final PackageManagerInternal mPackageManagerInternal;
305     private final UserManager mUserManager;
306     private final UsageStatsManagerInternal mUsageStatsManagerInternal;
307     private final ActivityManagerInternal mActivityManagerInternal;
308 
309     private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor;
310     private final ShortcutBitmapSaver mShortcutBitmapSaver;
311 
312     @GuardedBy("mLock")
313     final SparseIntArray mUidState = new SparseIntArray();
314 
315     @GuardedBy("mLock")
316     final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
317 
318     @GuardedBy("mLock")
319     private List<Integer> mDirtyUserIds = new ArrayList<>();
320 
321     private final AtomicBoolean mBootCompleted = new AtomicBoolean();
322 
323     private static final int PACKAGE_MATCH_FLAGS =
324             PackageManager.MATCH_DIRECT_BOOT_AWARE
325                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
326                     | PackageManager.MATCH_UNINSTALLED_PACKAGES;
327 
328     @GuardedBy("mLock")
329     final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray();
330 
331     // Stats
332     @VisibleForTesting
333     interface Stats {
334         int GET_DEFAULT_HOME = 0;
335         int GET_PACKAGE_INFO = 1;
336         int GET_PACKAGE_INFO_WITH_SIG = 2;
337         int GET_APPLICATION_INFO = 3;
338         int LAUNCHER_PERMISSION_CHECK = 4;
339         int CLEANUP_DANGLING_BITMAPS = 5;
340         int GET_ACTIVITY_WITH_METADATA = 6;
341         int GET_INSTALLED_PACKAGES = 7;
342         int CHECK_PACKAGE_CHANGES = 8;
343         int GET_APPLICATION_RESOURCES = 9;
344         int RESOURCE_NAME_LOOKUP = 10;
345         int GET_LAUNCHER_ACTIVITY = 11;
346         int CHECK_LAUNCHER_ACTIVITY = 12;
347         int IS_ACTIVITY_ENABLED = 13;
348         int PACKAGE_UPDATE_CHECK = 14;
349         int ASYNC_PRELOAD_USER_DELAY = 15;
350         int GET_DEFAULT_LAUNCHER = 16;
351 
352         int COUNT = GET_DEFAULT_LAUNCHER + 1;
353     }
354 
355     private static final String[] STAT_LABELS = {
356             "getHomeActivities()",
357             "Launcher permission check",
358             "getPackageInfo()",
359             "getPackageInfo(SIG)",
360             "getApplicationInfo",
361             "cleanupDanglingBitmaps",
362             "getActivity+metadata",
363             "getInstalledPackages",
364             "checkPackageChanges",
365             "getApplicationResources",
366             "resourceNameLookup",
367             "getLauncherActivity",
368             "checkLauncherActivity",
369             "isActivityEnabled",
370             "packageUpdateCheck",
371             "asyncPreloadUserDelay",
372             "getDefaultLauncher()"
373     };
374 
375     final Object mStatLock = new Object();
376 
377     @GuardedBy("mStatLock")
378     private final int[] mCountStats = new int[Stats.COUNT];
379 
380     @GuardedBy("mStatLock")
381     private final long[] mDurationStats = new long[Stats.COUNT];
382 
383     private static final int PROCESS_STATE_FOREGROUND_THRESHOLD =
384             ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
385 
386     static final int OPERATION_SET = 0;
387     static final int OPERATION_ADD = 1;
388     static final int OPERATION_UPDATE = 2;
389 
390     /** @hide */
391     @IntDef(value = {
392             OPERATION_SET,
393             OPERATION_ADD,
394             OPERATION_UPDATE
395     })
396     @Retention(RetentionPolicy.SOURCE)
397     @interface ShortcutOperation {
398     }
399 
400     @GuardedBy("mLock")
401     private int mWtfCount = 0;
402 
403     @GuardedBy("mLock")
404     private Exception mLastWtfStacktrace;
405 
406     static class InvalidFileFormatException extends Exception {
InvalidFileFormatException(String message, Throwable cause)407         public InvalidFileFormatException(String message, Throwable cause) {
408             super(message, cause);
409         }
410     }
411 
ShortcutService(Context context)412     public ShortcutService(Context context) {
413         this(context, BackgroundThread.get().getLooper(), /*onyForPackgeManagerApis*/ false);
414     }
415 
416     @VisibleForTesting
ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis)417     ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) {
418         mContext = Preconditions.checkNotNull(context);
419         LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
420         mHandler = new Handler(looper);
421         mIPackageManager = AppGlobals.getPackageManager();
422         mPackageManagerInternal = Preconditions.checkNotNull(
423                 LocalServices.getService(PackageManagerInternal.class));
424         mUserManager = Preconditions.checkNotNull(context.getSystemService(UserManager.class));
425         mUsageStatsManagerInternal = Preconditions.checkNotNull(
426                 LocalServices.getService(UsageStatsManagerInternal.class));
427         mActivityManagerInternal = Preconditions.checkNotNull(
428                 LocalServices.getService(ActivityManagerInternal.class));
429 
430         mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
431         mShortcutBitmapSaver = new ShortcutBitmapSaver(this);
432 
433         if (onlyForPackageManagerApis) {
434             return; // Don't do anything further.  For unit tests only.
435         }
436 
437         // Register receivers.
438 
439         // We need to set a priority, so let's just not use PackageMonitor for now.
440         // TODO Refactor PackageMonitor to support priorities.
441         final IntentFilter packageFilter = new IntentFilter();
442         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
443         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
444         packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
445         packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
446         packageFilter.addDataScheme("package");
447         packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
448         mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL,
449                 packageFilter, null, mHandler);
450 
451         final IntentFilter preferedActivityFilter = new IntentFilter();
452         preferedActivityFilter.addAction(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
453         preferedActivityFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
454         mContext.registerReceiverAsUser(mPackageMonitor, UserHandle.ALL,
455                 preferedActivityFilter, null, mHandler);
456 
457         final IntentFilter localeFilter = new IntentFilter();
458         localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
459         localeFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
460         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL,
461                 localeFilter, null, mHandler);
462 
463         injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
464                 | ActivityManager.UID_OBSERVER_GONE);
465     }
466 
logDurationStat(int statId, long start)467     void logDurationStat(int statId, long start) {
468         synchronized (mStatLock) {
469             mCountStats[statId]++;
470             mDurationStats[statId] += (injectElapsedRealtime() - start);
471         }
472     }
473 
injectGetLocaleTagsForUser(@serIdInt int userId)474     public String injectGetLocaleTagsForUser(@UserIdInt int userId) {
475         // TODO This should get the per-user locale.  b/30123329 b/30119489
476         return LocaleList.getDefault().toLanguageTags();
477     }
478 
479     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
480         @Override
481         public void onUidStateChanged(int uid, int procState, long procStateSeq)
482                 throws RemoteException {
483             handleOnUidStateChanged(uid, procState);
484         }
485 
486         @Override
487         public void onUidGone(int uid, boolean disabled) throws RemoteException {
488             handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
489         }
490 
491         @Override
492         public void onUidActive(int uid) throws RemoteException {
493         }
494 
495         @Override
496         public void onUidIdle(int uid, boolean disabled) throws RemoteException {
497         }
498     };
499 
handleOnUidStateChanged(int uid, int procState)500     void handleOnUidStateChanged(int uid, int procState) {
501         if (DEBUG_PROCSTATE) {
502             Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
503         }
504         synchronized (mLock) {
505             mUidState.put(uid, procState);
506 
507             // We need to keep track of last time an app comes to foreground.
508             // See ShortcutPackage.getApiCallCount() for how it's used.
509             // It doesn't have to be persisted, but it needs to be the elapsed time.
510             if (isProcessStateForeground(procState)) {
511                 mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime());
512             }
513         }
514     }
515 
isProcessStateForeground(int processState)516     private boolean isProcessStateForeground(int processState) {
517         return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
518     }
519 
isUidForegroundLocked(int uid)520     boolean isUidForegroundLocked(int uid) {
521         if (uid == Process.SYSTEM_UID) {
522             // IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
523             // so it's foreground anyway.
524             return true;
525         }
526         // First, check with the local cache.
527         if (isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE))) {
528             return true;
529         }
530         // If the cache says background, reach out to AM.  Since it'll internally need to hold
531         // the AM lock, we use it as a last resort.
532         return isProcessStateForeground(mActivityManagerInternal.getUidProcessState(uid));
533     }
534 
getUidLastForegroundElapsedTimeLocked(int uid)535     long getUidLastForegroundElapsedTimeLocked(int uid) {
536         return mUidLastForegroundElapsedTime.get(uid);
537     }
538 
539     /**
540      * System service lifecycle.
541      */
542     public static final class Lifecycle extends SystemService {
543         final ShortcutService mService;
544 
Lifecycle(Context context)545         public Lifecycle(Context context) {
546             super(context);
547             mService = new ShortcutService(context);
548         }
549 
550         @Override
onStart()551         public void onStart() {
552             publishBinderService(Context.SHORTCUT_SERVICE, mService);
553         }
554 
555         @Override
onBootPhase(int phase)556         public void onBootPhase(int phase) {
557             mService.onBootPhase(phase);
558         }
559 
560         @Override
onStopUser(int userHandle)561         public void onStopUser(int userHandle) {
562             mService.handleStopUser(userHandle);
563         }
564 
565         @Override
onUnlockUser(int userId)566         public void onUnlockUser(int userId) {
567             mService.handleUnlockUser(userId);
568         }
569     }
570 
571     /** lifecycle event */
onBootPhase(int phase)572     void onBootPhase(int phase) {
573         if (DEBUG) {
574             Slog.d(TAG, "onBootPhase: " + phase);
575         }
576         switch (phase) {
577             case SystemService.PHASE_LOCK_SETTINGS_READY:
578                 initialize();
579                 break;
580             case SystemService.PHASE_BOOT_COMPLETED:
581                 mBootCompleted.set(true);
582                 break;
583         }
584     }
585 
586     /** lifecycle event */
handleUnlockUser(int userId)587     void handleUnlockUser(int userId) {
588         if (DEBUG) {
589         Slog.d(TAG, "handleUnlockUser: user=" + userId);
590         }
591         synchronized (mLock) {
592             mUnlockedUsers.put(userId, true);
593         }
594 
595         // Preload the user data.
596         // Note, we don't use mHandler here but instead just start a new thread.
597         // This is because mHandler (which uses com.android.internal.os.BackgroundThread) is very
598         // busy at this point and this could take hundreds of milliseconds, which would be too
599         // late since the launcher would already have started.
600         // So we just create a new thread.  This code runs rarely, so we don't use a thread pool
601         // or anything.
602         final long start = injectElapsedRealtime();
603         injectRunOnNewThread(() -> {
604             synchronized (mLock) {
605                 logDurationStat(Stats.ASYNC_PRELOAD_USER_DELAY, start);
606                 getUserShortcutsLocked(userId);
607             }
608         });
609     }
610 
611     /** lifecycle event */
handleStopUser(int userId)612     void handleStopUser(int userId) {
613         if (DEBUG) {
614             Slog.d(TAG, "handleStopUser: user=" + userId);
615         }
616         synchronized (mLock) {
617             unloadUserLocked(userId);
618 
619             mUnlockedUsers.put(userId, false);
620         }
621     }
622 
unloadUserLocked(int userId)623     private void unloadUserLocked(int userId) {
624         if (DEBUG) {
625             Slog.d(TAG, "unloadUserLocked: user=" + userId);
626         }
627         // Save all dirty information.
628         saveDirtyInfo();
629 
630         // Unload
631         mUsers.delete(userId);
632     }
633 
634     /** Return the base state file name */
getBaseStateFile()635     private AtomicFile getBaseStateFile() {
636         final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
637         path.mkdirs();
638         return new AtomicFile(path);
639     }
640 
641     /**
642      * Init the instance. (load the state file, etc)
643      */
initialize()644     private void initialize() {
645         synchronized (mLock) {
646             loadConfigurationLocked();
647             loadBaseStateLocked();
648         }
649     }
650 
651     /**
652      * Load the configuration from Settings.
653      */
loadConfigurationLocked()654     private void loadConfigurationLocked() {
655         updateConfigurationLocked(injectShortcutManagerConstants());
656     }
657 
658     /**
659      * Load the configuration from Settings.
660      */
661     @VisibleForTesting
updateConfigurationLocked(String config)662     boolean updateConfigurationLocked(String config) {
663         boolean result = true;
664 
665         final KeyValueListParser parser = new KeyValueListParser(',');
666         try {
667             parser.setString(config);
668         } catch (IllegalArgumentException e) {
669             // Failed to parse the settings string, log this and move on
670             // with defaults.
671             Slog.e(TAG, "Bad shortcut manager settings", e);
672             result = false;
673         }
674 
675         mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
676                 DEFAULT_SAVE_DELAY_MS));
677 
678         mResetInterval = Math.max(1, parser.getLong(
679                 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
680                 * 1000L);
681 
682         mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
683                 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
684 
685         mMaxShortcuts = Math.max(0, (int) parser.getLong(
686                 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP));
687 
688         final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
689                 ? (int) parser.getLong(
690                 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
691                 DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
692                 : (int) parser.getLong(
693                 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
694                 DEFAULT_MAX_ICON_DIMENSION_DP));
695 
696         mMaxIconDimension = injectDipToPixel(iconDimensionDp);
697 
698         mIconPersistFormat = CompressFormat.valueOf(
699                 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
700 
701         mIconPersistQuality = (int) parser.getLong(
702                 ConfigConstants.KEY_ICON_QUALITY,
703                 DEFAULT_ICON_PERSIST_QUALITY);
704 
705         return result;
706     }
707 
708     @VisibleForTesting
injectShortcutManagerConstants()709     String injectShortcutManagerConstants() {
710         return android.provider.Settings.Global.getString(
711                 mContext.getContentResolver(),
712                 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
713     }
714 
715     @VisibleForTesting
injectDipToPixel(int dip)716     int injectDipToPixel(int dip) {
717         return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
718                 mContext.getResources().getDisplayMetrics());
719     }
720 
721     // === Persisting ===
722 
723     @Nullable
parseStringAttribute(XmlPullParser parser, String attribute)724     static String parseStringAttribute(XmlPullParser parser, String attribute) {
725         return parser.getAttributeValue(null, attribute);
726     }
727 
parseBooleanAttribute(XmlPullParser parser, String attribute)728     static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) {
729         return parseLongAttribute(parser, attribute) == 1;
730     }
731 
parseIntAttribute(XmlPullParser parser, String attribute)732     static int parseIntAttribute(XmlPullParser parser, String attribute) {
733         return (int) parseLongAttribute(parser, attribute);
734     }
735 
parseIntAttribute(XmlPullParser parser, String attribute, int def)736     static int parseIntAttribute(XmlPullParser parser, String attribute, int def) {
737         return (int) parseLongAttribute(parser, attribute, def);
738     }
739 
parseLongAttribute(XmlPullParser parser, String attribute)740     static long parseLongAttribute(XmlPullParser parser, String attribute) {
741         return parseLongAttribute(parser, attribute, 0);
742     }
743 
parseLongAttribute(XmlPullParser parser, String attribute, long def)744     static long parseLongAttribute(XmlPullParser parser, String attribute, long def) {
745         final String value = parseStringAttribute(parser, attribute);
746         if (TextUtils.isEmpty(value)) {
747             return def;
748         }
749         try {
750             return Long.parseLong(value);
751         } catch (NumberFormatException e) {
752             Slog.e(TAG, "Error parsing long " + value);
753             return def;
754         }
755     }
756 
757     @Nullable
parseComponentNameAttribute(XmlPullParser parser, String attribute)758     static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
759         final String value = parseStringAttribute(parser, attribute);
760         if (TextUtils.isEmpty(value)) {
761             return null;
762         }
763         return ComponentName.unflattenFromString(value);
764     }
765 
766     @Nullable
parseIntentAttributeNoDefault(XmlPullParser parser, String attribute)767     static Intent parseIntentAttributeNoDefault(XmlPullParser parser, String attribute) {
768         final String value = parseStringAttribute(parser, attribute);
769         Intent parsed = null;
770         if (!TextUtils.isEmpty(value)) {
771             try {
772                 parsed = Intent.parseUri(value, /* flags =*/ 0);
773             } catch (URISyntaxException e) {
774                 Slog.e(TAG, "Error parsing intent", e);
775             }
776         }
777         return parsed;
778     }
779 
780     @Nullable
parseIntentAttribute(XmlPullParser parser, String attribute)781     static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
782         Intent parsed = parseIntentAttributeNoDefault(parser, attribute);
783         if (parsed == null) {
784             // Default intent.
785             parsed = new Intent(Intent.ACTION_VIEW);
786         }
787         return parsed;
788     }
789 
writeTagValue(XmlSerializer out, String tag, String value)790     static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
791         if (TextUtils.isEmpty(value)) return;
792 
793         out.startTag(null, tag);
794         out.attribute(null, ATTR_VALUE, value);
795         out.endTag(null, tag);
796     }
797 
writeTagValue(XmlSerializer out, String tag, long value)798     static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
799         writeTagValue(out, tag, Long.toString(value));
800     }
801 
writeTagValue(XmlSerializer out, String tag, ComponentName name)802     static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
803         if (name == null) return;
804         writeTagValue(out, tag, name.flattenToString());
805     }
806 
writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)807     static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
808             throws IOException, XmlPullParserException {
809         if (bundle == null) return;
810 
811         out.startTag(null, tag);
812         bundle.saveToXml(out);
813         out.endTag(null, tag);
814     }
815 
writeAttr(XmlSerializer out, String name, CharSequence value)816     static void writeAttr(XmlSerializer out, String name, CharSequence value) throws IOException {
817         if (TextUtils.isEmpty(value)) return;
818 
819         out.attribute(null, name, value.toString());
820     }
821 
writeAttr(XmlSerializer out, String name, long value)822     static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
823         writeAttr(out, name, String.valueOf(value));
824     }
825 
writeAttr(XmlSerializer out, String name, boolean value)826     static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
827         if (value) {
828             writeAttr(out, name, "1");
829         }
830     }
831 
writeAttr(XmlSerializer out, String name, ComponentName comp)832     static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
833         if (comp == null) return;
834         writeAttr(out, name, comp.flattenToString());
835     }
836 
writeAttr(XmlSerializer out, String name, Intent intent)837     static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
838         if (intent == null) return;
839 
840         writeAttr(out, name, intent.toUri(/* flags =*/ 0));
841     }
842 
843     @VisibleForTesting
saveBaseStateLocked()844     void saveBaseStateLocked() {
845         final AtomicFile file = getBaseStateFile();
846         if (DEBUG) {
847             Slog.d(TAG, "Saving to " + file.getBaseFile());
848         }
849 
850         FileOutputStream outs = null;
851         try {
852             outs = file.startWrite();
853 
854             // Write to XML
855             XmlSerializer out = new FastXmlSerializer();
856             out.setOutput(outs, StandardCharsets.UTF_8.name());
857             out.startDocument(null, true);
858             out.startTag(null, TAG_ROOT);
859 
860             // Body.
861             writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
862 
863             // Epilogue.
864             out.endTag(null, TAG_ROOT);
865             out.endDocument();
866 
867             // Close.
868             file.finishWrite(outs);
869         } catch (IOException e) {
870             Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
871             file.failWrite(outs);
872         }
873     }
874 
loadBaseStateLocked()875     private void loadBaseStateLocked() {
876         mRawLastResetTime = 0;
877 
878         final AtomicFile file = getBaseStateFile();
879         if (DEBUG) {
880             Slog.d(TAG, "Loading from " + file.getBaseFile());
881         }
882         try (FileInputStream in = file.openRead()) {
883             XmlPullParser parser = Xml.newPullParser();
884             parser.setInput(in, StandardCharsets.UTF_8.name());
885 
886             int type;
887             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
888                 if (type != XmlPullParser.START_TAG) {
889                     continue;
890                 }
891                 final int depth = parser.getDepth();
892                 // Check the root tag
893                 final String tag = parser.getName();
894                 if (depth == 1) {
895                     if (!TAG_ROOT.equals(tag)) {
896                         Slog.e(TAG, "Invalid root tag: " + tag);
897                         return;
898                     }
899                     continue;
900                 }
901                 // Assume depth == 2
902                 switch (tag) {
903                     case TAG_LAST_RESET_TIME:
904                         mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
905                         break;
906                     default:
907                         Slog.e(TAG, "Invalid tag: " + tag);
908                         break;
909                 }
910             }
911         } catch (FileNotFoundException e) {
912             // Use the default
913         } catch (IOException | XmlPullParserException e) {
914             Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
915 
916             mRawLastResetTime = 0;
917         }
918         // Adjust the last reset time.
919         getLastResetTimeLocked();
920     }
921 
922     @VisibleForTesting
getUserFile(@serIdInt int userId)923     final File getUserFile(@UserIdInt int userId) {
924         return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
925     }
926 
saveUserLocked(@serIdInt int userId)927     private void saveUserLocked(@UserIdInt int userId) {
928         final File path = getUserFile(userId);
929         if (DEBUG) {
930             Slog.d(TAG, "Saving to " + path);
931         }
932 
933         mShortcutBitmapSaver.waitForAllSavesLocked();
934 
935         path.getParentFile().mkdirs();
936         final AtomicFile file = new AtomicFile(path);
937         FileOutputStream os = null;
938         try {
939             os = file.startWrite();
940 
941             saveUserInternalLocked(userId, os, /* forBackup= */ false);
942 
943             file.finishWrite(os);
944 
945             // Remove all dangling bitmap files.
946             cleanupDanglingBitmapDirectoriesLocked(userId);
947         } catch (XmlPullParserException | IOException e) {
948             Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
949             file.failWrite(os);
950         }
951     }
952 
saveUserInternalLocked(@serIdInt int userId, OutputStream os, boolean forBackup)953     private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
954             boolean forBackup) throws IOException, XmlPullParserException {
955 
956         final BufferedOutputStream bos = new BufferedOutputStream(os);
957 
958         // Write to XML
959         XmlSerializer out = new FastXmlSerializer();
960         out.setOutput(bos, StandardCharsets.UTF_8.name());
961         out.startDocument(null, true);
962 
963         getUserShortcutsLocked(userId).saveToXml(out, forBackup);
964 
965         out.endDocument();
966 
967         bos.flush();
968         os.flush();
969     }
970 
throwForInvalidTag(int depth, String tag)971     static IOException throwForInvalidTag(int depth, String tag) throws IOException {
972         throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
973     }
974 
warnForInvalidTag(int depth, String tag)975     static void warnForInvalidTag(int depth, String tag) throws IOException {
976         Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
977     }
978 
979     @Nullable
loadUserLocked(@serIdInt int userId)980     private ShortcutUser loadUserLocked(@UserIdInt int userId) {
981         final File path = getUserFile(userId);
982         if (DEBUG) {
983             Slog.d(TAG, "Loading from " + path);
984         }
985         final AtomicFile file = new AtomicFile(path);
986 
987         final FileInputStream in;
988         try {
989             in = file.openRead();
990         } catch (FileNotFoundException e) {
991             if (DEBUG) {
992                 Slog.d(TAG, "Not found " + path);
993             }
994             return null;
995         }
996         try {
997             final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
998             return ret;
999         } catch (IOException | XmlPullParserException | InvalidFileFormatException e) {
1000             Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
1001             return null;
1002         } finally {
1003             IoUtils.closeQuietly(in);
1004         }
1005     }
1006 
loadUserInternal(@serIdInt int userId, InputStream is, boolean fromBackup)1007     private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
1008             boolean fromBackup) throws XmlPullParserException, IOException,
1009             InvalidFileFormatException {
1010 
1011         final BufferedInputStream bis = new BufferedInputStream(is);
1012 
1013         ShortcutUser ret = null;
1014         XmlPullParser parser = Xml.newPullParser();
1015         parser.setInput(bis, StandardCharsets.UTF_8.name());
1016 
1017         int type;
1018         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
1019             if (type != XmlPullParser.START_TAG) {
1020                 continue;
1021             }
1022             final int depth = parser.getDepth();
1023 
1024             final String tag = parser.getName();
1025             if (DEBUG_LOAD) {
1026                 Slog.d(TAG, String.format("depth=%d type=%d name=%s",
1027                         depth, type, tag));
1028             }
1029             if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
1030                 ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
1031                 continue;
1032             }
1033             throwForInvalidTag(depth, tag);
1034         }
1035         return ret;
1036     }
1037 
scheduleSaveBaseState()1038     private void scheduleSaveBaseState() {
1039         scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
1040     }
1041 
scheduleSaveUser(@serIdInt int userId)1042     void scheduleSaveUser(@UserIdInt int userId) {
1043         scheduleSaveInner(userId);
1044     }
1045 
1046     // In order to re-schedule, we need to reuse the same instance, so keep it in final.
1047     private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo;
1048 
scheduleSaveInner(@serIdInt int userId)1049     private void scheduleSaveInner(@UserIdInt int userId) {
1050         if (DEBUG) {
1051             Slog.d(TAG, "Scheduling to save for " + userId);
1052         }
1053         synchronized (mLock) {
1054             if (!mDirtyUserIds.contains(userId)) {
1055                 mDirtyUserIds.add(userId);
1056             }
1057         }
1058         // If already scheduled, remove that and re-schedule in N seconds.
1059         mHandler.removeCallbacks(mSaveDirtyInfoRunner);
1060         mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
1061     }
1062 
1063     @VisibleForTesting
saveDirtyInfo()1064     void saveDirtyInfo() {
1065         if (DEBUG) {
1066             Slog.d(TAG, "saveDirtyInfo");
1067         }
1068         try {
1069             synchronized (mLock) {
1070                 for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
1071                     final int userId = mDirtyUserIds.get(i);
1072                     if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
1073                         saveBaseStateLocked();
1074                     } else {
1075                         saveUserLocked(userId);
1076                     }
1077                 }
1078                 mDirtyUserIds.clear();
1079             }
1080         } catch (Exception e) {
1081             wtf("Exception in saveDirtyInfo", e);
1082         }
1083     }
1084 
1085     /** Return the last reset time. */
getLastResetTimeLocked()1086     long getLastResetTimeLocked() {
1087         updateTimesLocked();
1088         return mRawLastResetTime;
1089     }
1090 
1091     /** Return the next reset time. */
getNextResetTimeLocked()1092     long getNextResetTimeLocked() {
1093         updateTimesLocked();
1094         return mRawLastResetTime + mResetInterval;
1095     }
1096 
isClockValid(long time)1097     static boolean isClockValid(long time) {
1098         return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
1099     }
1100 
1101     /**
1102      * Update the last reset time.
1103      */
updateTimesLocked()1104     private void updateTimesLocked() {
1105 
1106         final long now = injectCurrentTimeMillis();
1107 
1108         final long prevLastResetTime = mRawLastResetTime;
1109 
1110         if (mRawLastResetTime == 0) { // first launch.
1111             // TODO Randomize??
1112             mRawLastResetTime = now;
1113         } else if (now < mRawLastResetTime) {
1114             // Clock rewound.
1115             if (isClockValid(now)) {
1116                 Slog.w(TAG, "Clock rewound");
1117                 // TODO Randomize??
1118                 mRawLastResetTime = now;
1119             }
1120         } else {
1121             if ((mRawLastResetTime + mResetInterval) <= now) {
1122                 final long offset = mRawLastResetTime % mResetInterval;
1123                 mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
1124             }
1125         }
1126         if (prevLastResetTime != mRawLastResetTime) {
1127             scheduleSaveBaseState();
1128         }
1129     }
1130 
1131     // Requires mLock held, but "Locked" prefix would look weired so we just say "L".
isUserUnlockedL(@serIdInt int userId)1132     protected boolean isUserUnlockedL(@UserIdInt int userId) {
1133         // First, check the local copy.
1134         if (mUnlockedUsers.get(userId)) {
1135             return true;
1136         }
1137         // If the local copy says the user is locked, check with AM for the actual state, since
1138         // the user might just have been unlocked.
1139         // Note we just don't use isUserUnlockingOrUnlocked() here, because it'll return false
1140         // when the user is STOPPING, which we still want to consider as "unlocked".
1141         final long token = injectClearCallingIdentity();
1142         try {
1143             return mUserManager.isUserUnlockingOrUnlocked(userId);
1144         } finally {
1145             injectRestoreCallingIdentity(token);
1146         }
1147     }
1148 
1149     // Requires mLock held, but "Locked" prefix would look weired so we jsut say "L".
throwIfUserLockedL(@serIdInt int userId)1150     void throwIfUserLockedL(@UserIdInt int userId) {
1151         if (!isUserUnlockedL(userId)) {
1152             throw new IllegalStateException("User " + userId + " is locked or not running");
1153         }
1154     }
1155 
1156     @GuardedBy("mLock")
1157     @NonNull
isUserLoadedLocked(@serIdInt int userId)1158     private boolean isUserLoadedLocked(@UserIdInt int userId) {
1159         return mUsers.get(userId) != null;
1160     }
1161 
1162     /** Return the per-user state. */
1163     @GuardedBy("mLock")
1164     @NonNull
getUserShortcutsLocked(@serIdInt int userId)1165     ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
1166         if (!isUserUnlockedL(userId)) {
1167             wtf("User still locked");
1168         }
1169 
1170         ShortcutUser userPackages = mUsers.get(userId);
1171         if (userPackages == null) {
1172             userPackages = loadUserLocked(userId);
1173             if (userPackages == null) {
1174                 userPackages = new ShortcutUser(this, userId);
1175             }
1176             mUsers.put(userId, userPackages);
1177 
1178             // Also when a user's data is first accessed, scan all packages.
1179             checkPackageChanges(userId);
1180         }
1181         return userPackages;
1182     }
1183 
forEachLoadedUserLocked(@onNull Consumer<ShortcutUser> c)1184     void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
1185         for (int i = mUsers.size() - 1; i >= 0; i--) {
1186             c.accept(mUsers.valueAt(i));
1187         }
1188     }
1189 
1190     /**
1191      * Return the per-user per-package state.  If the caller is a publisher, use
1192      * {@link #getPackageShortcutsForPublisherLocked} instead.
1193      */
1194     @GuardedBy("mLock")
1195     @NonNull
getPackageShortcutsLocked( @onNull String packageName, @UserIdInt int userId)1196     ShortcutPackage getPackageShortcutsLocked(
1197             @NonNull String packageName, @UserIdInt int userId) {
1198         return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
1199     }
1200 
1201     /** Return the per-user per-package state.  Use this when the caller is a publisher. */
1202     @GuardedBy("mLock")
1203     @NonNull
getPackageShortcutsForPublisherLocked( @onNull String packageName, @UserIdInt int userId)1204     ShortcutPackage getPackageShortcutsForPublisherLocked(
1205             @NonNull String packageName, @UserIdInt int userId) {
1206         final ShortcutPackage ret = getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
1207         ret.getUser().onCalledByPublisher(packageName);
1208         return ret;
1209     }
1210 
1211     @GuardedBy("mLock")
1212     @NonNull
getLauncherShortcutsLocked( @onNull String packageName, @UserIdInt int ownerUserId, @UserIdInt int launcherUserId)1213     ShortcutLauncher getLauncherShortcutsLocked(
1214             @NonNull String packageName, @UserIdInt int ownerUserId,
1215             @UserIdInt int launcherUserId) {
1216         return getUserShortcutsLocked(ownerUserId)
1217                 .getLauncherShortcuts(packageName, launcherUserId);
1218     }
1219 
1220     // === Caller validation ===
1221 
removeIconLocked(ShortcutInfo shortcut)1222     void removeIconLocked(ShortcutInfo shortcut) {
1223         mShortcutBitmapSaver.removeIcon(shortcut);
1224     }
1225 
cleanupBitmapsForPackage(@serIdInt int userId, String packageName)1226     public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
1227         final File packagePath = new File(getUserBitmapFilePath(userId), packageName);
1228         if (!packagePath.isDirectory()) {
1229             return;
1230         }
1231         if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) {
1232             Slog.w(TAG, "Unable to remove directory " + packagePath);
1233         }
1234     }
1235 
1236     /**
1237      * Remove dangling bitmap files for a user.
1238      *
1239      * Note this method must be called with the lock held after calling
1240      * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
1241      * saves are going on.
1242      */
cleanupDanglingBitmapDirectoriesLocked(@serIdInt int userId)1243     private void cleanupDanglingBitmapDirectoriesLocked(@UserIdInt int userId) {
1244         if (DEBUG) {
1245             Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
1246         }
1247         final long start = injectElapsedRealtime();
1248 
1249         final ShortcutUser user = getUserShortcutsLocked(userId);
1250 
1251         final File bitmapDir = getUserBitmapFilePath(userId);
1252         final File[] children = bitmapDir.listFiles();
1253         if (children == null) {
1254             return;
1255         }
1256         for (File child : children) {
1257             if (!child.isDirectory()) {
1258                 continue;
1259             }
1260             final String packageName = child.getName();
1261             if (DEBUG) {
1262                 Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName);
1263             }
1264             if (!user.hasPackage(packageName)) {
1265                 if (DEBUG) {
1266                     Slog.d(TAG, "Removing dangling bitmap directory: " + packageName);
1267                 }
1268                 cleanupBitmapsForPackage(userId, packageName);
1269             } else {
1270                 cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
1271             }
1272         }
1273         logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
1274     }
1275 
1276     /**
1277      * Remove dangling bitmap files for a package.
1278      *
1279      * Note this method must be called with the lock held after calling
1280      * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap
1281      * saves are going on.
1282      */
cleanupDanglingBitmapFilesLocked(@serIdInt int userId, @NonNull ShortcutUser user, @NonNull String packageName, @NonNull File path)1283     private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
1284             @NonNull String packageName, @NonNull File path) {
1285         final ArraySet<String> usedFiles =
1286                 user.getPackageShortcuts(packageName).getUsedBitmapFiles();
1287 
1288         for (File child : path.listFiles()) {
1289             if (!child.isFile()) {
1290                 continue;
1291             }
1292             final String name = child.getName();
1293             if (!usedFiles.contains(name)) {
1294                 if (DEBUG) {
1295                     Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
1296                 }
1297                 child.delete();
1298             }
1299         }
1300     }
1301 
1302     @VisibleForTesting
1303     static class FileOutputStreamWithPath extends FileOutputStream {
1304         private final File mFile;
1305 
FileOutputStreamWithPath(File file)1306         public FileOutputStreamWithPath(File file) throws FileNotFoundException {
1307             super(file);
1308             mFile = file;
1309         }
1310 
getFile()1311         public File getFile() {
1312             return mFile;
1313         }
1314     }
1315 
1316     /**
1317      * Build the cached bitmap filename for a shortcut icon.
1318      *
1319      * The filename will be based on the ID, except certain characters will be escaped.
1320      */
openIconFileForWrite(@serIdInt int userId, ShortcutInfo shortcut)1321     FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
1322             throws IOException {
1323         final File packagePath = new File(getUserBitmapFilePath(userId),
1324                 shortcut.getPackage());
1325         if (!packagePath.isDirectory()) {
1326             packagePath.mkdirs();
1327             if (!packagePath.isDirectory()) {
1328                 throw new IOException("Unable to create directory " + packagePath);
1329             }
1330             SELinux.restorecon(packagePath);
1331         }
1332 
1333         final String baseName = String.valueOf(injectCurrentTimeMillis());
1334         for (int suffix = 0; ; suffix++) {
1335             final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
1336             final File file = new File(packagePath, filename);
1337             if (!file.exists()) {
1338                 if (DEBUG) {
1339                     Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
1340                 }
1341                 return new FileOutputStreamWithPath(file);
1342             }
1343         }
1344     }
1345 
saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut)1346     void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) {
1347         if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
1348             return;
1349         }
1350 
1351         final long token = injectClearCallingIdentity();
1352         try {
1353             // Clear icon info on the shortcut.
1354             removeIconLocked(shortcut);
1355 
1356             final Icon icon = shortcut.getIcon();
1357             if (icon == null) {
1358                 return; // has no icon
1359             }
1360             int maxIconDimension = mMaxIconDimension;
1361             Bitmap bitmap;
1362             try {
1363                 switch (icon.getType()) {
1364                     case Icon.TYPE_RESOURCE: {
1365                         injectValidateIconResPackage(shortcut, icon);
1366 
1367                         shortcut.setIconResourceId(icon.getResId());
1368                         shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
1369                         return;
1370                     }
1371                     case Icon.TYPE_BITMAP:
1372                         bitmap = icon.getBitmap(); // Don't recycle in this case.
1373                         break;
1374                     case Icon.TYPE_ADAPTIVE_BITMAP: {
1375                         bitmap = icon.getBitmap(); // Don't recycle in this case.
1376                         maxIconDimension *= (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
1377                         break;
1378                     }
1379                     default:
1380                         // This shouldn't happen because we've already validated the icon, but
1381                         // just in case.
1382                         throw ShortcutInfo.getInvalidIconException();
1383                 }
1384                 mShortcutBitmapSaver.saveBitmapLocked(shortcut,
1385                         maxIconDimension, mIconPersistFormat, mIconPersistQuality);
1386             } finally {
1387                 // Once saved, we won't use the original icon information, so null it out.
1388                 shortcut.clearIcon();
1389             }
1390         } finally {
1391             injectRestoreCallingIdentity(token);
1392         }
1393     }
1394 
1395     // Unfortunately we can't do this check in unit tests because we fake creator package names,
1396     // so override in unit tests.
1397     // TODO CTS this case.
injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon)1398     void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
1399         if (!shortcut.getPackage().equals(icon.getResPackage())) {
1400             throw new IllegalArgumentException(
1401                     "Icon resource must reside in shortcut owner package");
1402         }
1403     }
1404 
shrinkBitmap(Bitmap in, int maxSize)1405     static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
1406         // Original width/height.
1407         final int ow = in.getWidth();
1408         final int oh = in.getHeight();
1409         if ((ow <= maxSize) && (oh <= maxSize)) {
1410             if (DEBUG) {
1411                 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
1412             }
1413             return in;
1414         }
1415         final int longerDimension = Math.max(ow, oh);
1416 
1417         // New width and height.
1418         final int nw = ow * maxSize / longerDimension;
1419         final int nh = oh * maxSize / longerDimension;
1420         if (DEBUG) {
1421             Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
1422                     ow, oh, nw, nh));
1423         }
1424 
1425         final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
1426         final Canvas c = new Canvas(scaledBitmap);
1427 
1428         final RectF dst = new RectF(0, 0, nw, nh);
1429 
1430         c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
1431 
1432         return scaledBitmap;
1433     }
1434 
1435     /**
1436      * For a shortcut, update all resource names from resource IDs, and also update all
1437      * resource-based strings.
1438      */
fixUpShortcutResourceNamesAndValues(ShortcutInfo si)1439     void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) {
1440         final Resources publisherRes = injectGetResourcesForApplicationAsUser(
1441                 si.getPackage(), si.getUserId());
1442         if (publisherRes != null) {
1443             final long start = injectElapsedRealtime();
1444             try {
1445                 si.lookupAndFillInResourceNames(publisherRes);
1446             } finally {
1447                 logDurationStat(Stats.RESOURCE_NAME_LOOKUP, start);
1448             }
1449             si.resolveResourceStrings(publisherRes);
1450         }
1451     }
1452 
1453     // === Caller validation ===
1454 
isCallerSystem()1455     private boolean isCallerSystem() {
1456         final int callingUid = injectBinderCallingUid();
1457         return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
1458     }
1459 
isCallerShell()1460     private boolean isCallerShell() {
1461         final int callingUid = injectBinderCallingUid();
1462         return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
1463     }
1464 
enforceSystemOrShell()1465     private void enforceSystemOrShell() {
1466         if (!(isCallerSystem() || isCallerShell())) {
1467             throw new SecurityException("Caller must be system or shell");
1468         }
1469     }
1470 
enforceShell()1471     private void enforceShell() {
1472         if (!isCallerShell()) {
1473             throw new SecurityException("Caller must be shell");
1474         }
1475     }
1476 
enforceSystem()1477     private void enforceSystem() {
1478         if (!isCallerSystem()) {
1479             throw new SecurityException("Caller must be system");
1480         }
1481     }
1482 
enforceResetThrottlingPermission()1483     private void enforceResetThrottlingPermission() {
1484         if (isCallerSystem()) {
1485             return;
1486         }
1487         enforceCallingOrSelfPermission(
1488                 android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null);
1489     }
1490 
enforceCallingOrSelfPermission( @onNull String permission, @Nullable String message)1491     private void enforceCallingOrSelfPermission(
1492             @NonNull String permission, @Nullable String message) {
1493         if (isCallerSystem()) {
1494             return;
1495         }
1496         injectEnforceCallingPermission(permission, message);
1497     }
1498 
1499     /**
1500      * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse
1501      * mockito.  So instead we extracted it here and override it in the tests.
1502      */
1503     @VisibleForTesting
injectEnforceCallingPermission( @onNull String permission, @Nullable String message)1504     void injectEnforceCallingPermission(
1505             @NonNull String permission, @Nullable String message) {
1506         mContext.enforceCallingPermission(permission, message);
1507     }
1508 
verifyCaller(@onNull String packageName, @UserIdInt int userId)1509     private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
1510         Preconditions.checkStringNotEmpty(packageName, "packageName");
1511 
1512         if (isCallerSystem()) {
1513             return; // no check
1514         }
1515 
1516         final int callingUid = injectBinderCallingUid();
1517 
1518         // Otherwise, make sure the arguments are valid.
1519         if (UserHandle.getUserId(callingUid) != userId) {
1520             throw new SecurityException("Invalid user-ID");
1521         }
1522         if (injectGetPackageUid(packageName, userId) != callingUid) {
1523             throw new SecurityException("Calling package name mismatch");
1524         }
1525         Preconditions.checkState(!isEphemeralApp(packageName, userId),
1526                 "Ephemeral apps can't use ShortcutManager");
1527     }
1528 
1529     // Overridden in unit tests to execute r synchronously.
injectPostToHandler(Runnable r)1530     void injectPostToHandler(Runnable r) {
1531         mHandler.post(r);
1532     }
1533 
injectRunOnNewThread(Runnable r)1534     void injectRunOnNewThread(Runnable r) {
1535         new Thread(r).start();
1536     }
1537 
1538     /**
1539      * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
1540      *                                  {@link #getMaxActivityShortcuts()}.
1541      */
enforceMaxActivityShortcuts(int numShortcuts)1542     void enforceMaxActivityShortcuts(int numShortcuts) {
1543         if (numShortcuts > mMaxShortcuts) {
1544             throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
1545         }
1546     }
1547 
1548     /**
1549      * Return the max number of dynamic + manifest shortcuts for each launcher icon.
1550      */
getMaxActivityShortcuts()1551     int getMaxActivityShortcuts() {
1552         return mMaxShortcuts;
1553     }
1554 
1555     /**
1556      * - Sends a notification to LauncherApps
1557      * - Write to file
1558      */
packageShortcutsChanged(@onNull String packageName, @UserIdInt int userId)1559     void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) {
1560         notifyListeners(packageName, userId);
1561         scheduleSaveUser(userId);
1562     }
1563 
notifyListeners(@onNull String packageName, @UserIdInt int userId)1564     private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
1565         if (DEBUG) {
1566             Slog.d(TAG, String.format(
1567                     "Shortcut changes: package=%s, user=%d", packageName, userId));
1568         }
1569         injectPostToHandler(() -> {
1570             try {
1571                 final ArrayList<ShortcutChangeListener> copy;
1572                 synchronized (mLock) {
1573                     if (!isUserUnlockedL(userId)) {
1574                         return;
1575                     }
1576 
1577                     copy = new ArrayList<>(mListeners);
1578                 }
1579                 // Note onShortcutChanged() needs to be called with the system service permissions.
1580                 for (int i = copy.size() - 1; i >= 0; i--) {
1581                     copy.get(i).onShortcutChanged(packageName, userId);
1582                 }
1583             } catch (Exception ignore) {
1584             }
1585         });
1586     }
1587 
1588     /**
1589      * Clean up / validate an incoming shortcut.
1590      * - Make sure all mandatory fields are set.
1591      * - Make sure the intent's extras are persistable, and them to set
1592      * {@link ShortcutInfo#mIntentPersistableExtrases}.  Also clear its extras.
1593      * - Clear flags.
1594      */
fixUpIncomingShortcutInfo(@onNull ShortcutInfo shortcut, boolean forUpdate, boolean forPinRequest)1595     private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate,
1596             boolean forPinRequest) {
1597         if (shortcut.isReturnedByServer()) {
1598             Log.w(TAG,
1599                     "Re-publishing ShortcutInfo returned by server is not supported."
1600                     + " Some information such as icon may lost from shortcut.");
1601         }
1602         Preconditions.checkNotNull(shortcut, "Null shortcut detected");
1603         if (shortcut.getActivity() != null) {
1604             Preconditions.checkState(
1605                     shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
1606                     "Cannot publish shortcut: activity " + shortcut.getActivity() + " does not"
1607                     + " belong to package " + shortcut.getPackage());
1608             Preconditions.checkState(
1609                     injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()),
1610                     "Cannot publish shortcut: activity " + shortcut.getActivity() + " is not"
1611                             + " main activity");
1612         }
1613 
1614         if (!forUpdate) {
1615             shortcut.enforceMandatoryFields(/* forPinned= */ forPinRequest);
1616             if (!forPinRequest) {
1617                 Preconditions.checkState(shortcut.getActivity() != null,
1618                         "Cannot publish shortcut: target activity is not set");
1619             }
1620         }
1621         if (shortcut.getIcon() != null) {
1622             ShortcutInfo.validateIcon(shortcut.getIcon());
1623         }
1624 
1625         shortcut.replaceFlags(0);
1626     }
1627 
fixUpIncomingShortcutInfo(@onNull ShortcutInfo shortcut, boolean forUpdate)1628     private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
1629         fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false);
1630     }
1631 
validateShortcutForPinRequest(@onNull ShortcutInfo shortcut)1632     public void validateShortcutForPinRequest(@NonNull ShortcutInfo shortcut) {
1633         fixUpIncomingShortcutInfo(shortcut, /* forUpdate= */ false, /*forPinRequest=*/ true);
1634     }
1635 
1636     /**
1637      * When a shortcut has no target activity, set the default one from the package.
1638      */
fillInDefaultActivity(List<ShortcutInfo> shortcuts)1639     private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) {
1640         ComponentName defaultActivity = null;
1641         for (int i = shortcuts.size() - 1; i >= 0; i--) {
1642             final ShortcutInfo si = shortcuts.get(i);
1643             if (si.getActivity() == null) {
1644                 if (defaultActivity == null) {
1645                     defaultActivity = injectGetDefaultMainActivity(
1646                             si.getPackage(), si.getUserId());
1647                     Preconditions.checkState(defaultActivity != null,
1648                             "Launcher activity not found for package " + si.getPackage());
1649                 }
1650                 si.setActivity(defaultActivity);
1651             }
1652         }
1653     }
1654 
assignImplicitRanks(List<ShortcutInfo> shortcuts)1655     private void assignImplicitRanks(List<ShortcutInfo> shortcuts) {
1656         for (int i = shortcuts.size() - 1; i >= 0; i--) {
1657             shortcuts.get(i).setImplicitRank(i);
1658         }
1659     }
1660 
setReturnedByServer(List<ShortcutInfo> shortcuts)1661     private List<ShortcutInfo> setReturnedByServer(List<ShortcutInfo> shortcuts) {
1662         for (int i = shortcuts.size() - 1; i >= 0; i--) {
1663             shortcuts.get(i).setReturnedByServer();
1664         }
1665         return shortcuts;
1666     }
1667 
1668     // === APIs ===
1669 
1670     @Override
setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)1671     public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1672             @UserIdInt int userId) {
1673         verifyCaller(packageName, userId);
1674 
1675         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1676         final int size = newShortcuts.size();
1677 
1678         synchronized (mLock) {
1679             throwIfUserLockedL(userId);
1680 
1681             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1682 
1683             ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1684 
1685             fillInDefaultActivity(newShortcuts);
1686 
1687             ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
1688 
1689             // Throttling.
1690             if (!ps.tryApiCall()) {
1691                 return false;
1692             }
1693 
1694             // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1695             ps.clearAllImplicitRanks();
1696             assignImplicitRanks(newShortcuts);
1697 
1698             for (int i = 0; i < size; i++) {
1699                 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
1700             }
1701 
1702             // First, remove all un-pinned; dynamic shortcuts
1703             ps.deleteAllDynamicShortcuts();
1704 
1705             // Then, add/update all.  We need to make sure to take over "pinned" flag.
1706             for (int i = 0; i < size; i++) {
1707                 final ShortcutInfo newShortcut = newShortcuts.get(i);
1708                 ps.addOrUpdateDynamicShortcut(newShortcut);
1709             }
1710 
1711             // Lastly, adjust the ranks.
1712             ps.adjustRanks();
1713         }
1714         packageShortcutsChanged(packageName, userId);
1715 
1716         verifyStates();
1717 
1718         return true;
1719     }
1720 
1721     @Override
updateShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)1722     public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1723             @UserIdInt int userId) {
1724         verifyCaller(packageName, userId);
1725 
1726         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1727         final int size = newShortcuts.size();
1728 
1729         synchronized (mLock) {
1730             throwIfUserLockedL(userId);
1731 
1732             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1733 
1734             ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1735 
1736             // For update, don't fill in the default activity.  Having null activity means
1737             // "don't update the activity" here.
1738 
1739             ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
1740 
1741             // Throttling.
1742             if (!ps.tryApiCall()) {
1743                 return false;
1744             }
1745 
1746             // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1747             ps.clearAllImplicitRanks();
1748             assignImplicitRanks(newShortcuts);
1749 
1750             for (int i = 0; i < size; i++) {
1751                 final ShortcutInfo source = newShortcuts.get(i);
1752                 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
1753 
1754                 final ShortcutInfo target = ps.findShortcutById(source.getId());
1755                 if (target == null) {
1756                     continue;
1757                 }
1758 
1759                 if (target.isEnabled() != source.isEnabled()) {
1760                     Slog.w(TAG,
1761                             "ShortcutInfo.enabled cannot be changed with updateShortcuts()");
1762                 }
1763 
1764                 // When updating the rank, we need to insert between existing ranks, so set
1765                 // this setRankChanged, and also copy the implicit rank fo adjustRanks().
1766                 if (source.hasRank()) {
1767                     target.setRankChanged();
1768                     target.setImplicitRank(source.getImplicitRank());
1769                 }
1770 
1771                 final boolean replacingIcon = (source.getIcon() != null);
1772                 if (replacingIcon) {
1773                     removeIconLocked(target);
1774                 }
1775 
1776                 // Note copyNonNullFieldsFrom() does the "updatable with?" check too.
1777                 target.copyNonNullFieldsFrom(source);
1778                 target.setTimestamp(injectCurrentTimeMillis());
1779 
1780                 if (replacingIcon) {
1781                     saveIconAndFixUpShortcutLocked(target);
1782                 }
1783 
1784                 // When we're updating any resource related fields, re-extract the res names and
1785                 // the values.
1786                 if (replacingIcon || source.hasStringResources()) {
1787                     fixUpShortcutResourceNamesAndValues(target);
1788                 }
1789             }
1790 
1791             // Lastly, adjust the ranks.
1792             ps.adjustRanks();
1793         }
1794         packageShortcutsChanged(packageName, userId);
1795 
1796         verifyStates();
1797 
1798         return true;
1799     }
1800 
1801     @Override
addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList, @UserIdInt int userId)1802     public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1803             @UserIdInt int userId) {
1804         verifyCaller(packageName, userId);
1805 
1806         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1807         final int size = newShortcuts.size();
1808 
1809         synchronized (mLock) {
1810             throwIfUserLockedL(userId);
1811 
1812             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1813 
1814             ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1815 
1816             fillInDefaultActivity(newShortcuts);
1817 
1818             ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
1819 
1820             // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1821             ps.clearAllImplicitRanks();
1822             assignImplicitRanks(newShortcuts);
1823 
1824             // Throttling.
1825             if (!ps.tryApiCall()) {
1826                 return false;
1827             }
1828             for (int i = 0; i < size; i++) {
1829                 final ShortcutInfo newShortcut = newShortcuts.get(i);
1830 
1831                 // Validate the shortcut.
1832                 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
1833 
1834                 // When ranks are changing, we need to insert between ranks, so set the
1835                 // "rank changed" flag.
1836                 newShortcut.setRankChanged();
1837 
1838                 // Add it.
1839                 ps.addOrUpdateDynamicShortcut(newShortcut);
1840             }
1841 
1842             // Lastly, adjust the ranks.
1843             ps.adjustRanks();
1844         }
1845         packageShortcutsChanged(packageName, userId);
1846 
1847         verifyStates();
1848 
1849         return true;
1850     }
1851 
1852     @Override
requestPinShortcut(String packageName, ShortcutInfo shortcut, IntentSender resultIntent, int userId)1853     public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut,
1854             IntentSender resultIntent, int userId) {
1855         Preconditions.checkNotNull(shortcut);
1856         Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
1857         return requestPinItem(packageName, userId, shortcut, null, null, resultIntent);
1858     }
1859 
1860     @Override
createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId)1861     public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId)
1862             throws RemoteException {
1863         Preconditions.checkNotNull(shortcut);
1864         Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
1865         verifyCaller(packageName, userId);
1866 
1867         final Intent ret;
1868         synchronized (mLock) {
1869             throwIfUserLockedL(userId);
1870 
1871             // Send request to the launcher, if supported.
1872             ret = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId);
1873         }
1874 
1875         verifyStates();
1876         return ret;
1877     }
1878 
1879     /**
1880      * Handles {@link #requestPinShortcut} and {@link ShortcutServiceInternal#requestPinAppWidget}.
1881      * After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}.
1882      * Either {@param shortcut} or {@param appWidget} should be non-null.
1883      */
requestPinItem(String packageName, int userId, ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent)1884     private boolean requestPinItem(String packageName, int userId, ShortcutInfo shortcut,
1885             AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) {
1886         verifyCaller(packageName, userId);
1887 
1888         final boolean ret;
1889         synchronized (mLock) {
1890             throwIfUserLockedL(userId);
1891 
1892             Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()),
1893                     "Calling application must have a foreground activity or a foreground service");
1894 
1895             // Send request to the launcher, if supported.
1896             ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras,
1897                     userId, resultIntent);
1898         }
1899 
1900         verifyStates();
1901 
1902         return ret;
1903     }
1904 
1905     @Override
disableShortcuts(String packageName, List shortcutIds, CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId)1906     public void disableShortcuts(String packageName, List shortcutIds,
1907             CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) {
1908         verifyCaller(packageName, userId);
1909         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1910 
1911         synchronized (mLock) {
1912             throwIfUserLockedL(userId);
1913 
1914             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1915 
1916             ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1917 
1918             final String disabledMessageString =
1919                     (disabledMessage == null) ? null : disabledMessage.toString();
1920 
1921             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1922                 ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
1923                         disabledMessageString, disabledMessageResId,
1924                         /* overrideImmutable=*/ false);
1925             }
1926 
1927             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
1928             ps.adjustRanks();
1929         }
1930         packageShortcutsChanged(packageName, userId);
1931 
1932         verifyStates();
1933     }
1934 
1935     @Override
enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId)1936     public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) {
1937         verifyCaller(packageName, userId);
1938         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1939 
1940         synchronized (mLock) {
1941             throwIfUserLockedL(userId);
1942 
1943             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1944 
1945             ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1946 
1947             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1948                 ps.enableWithId((String) shortcutIds.get(i));
1949             }
1950         }
1951         packageShortcutsChanged(packageName, userId);
1952 
1953         verifyStates();
1954     }
1955 
1956     @Override
removeDynamicShortcuts(String packageName, List shortcutIds, @UserIdInt int userId)1957     public void removeDynamicShortcuts(String packageName, List shortcutIds,
1958             @UserIdInt int userId) {
1959         verifyCaller(packageName, userId);
1960         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1961 
1962         synchronized (mLock) {
1963             throwIfUserLockedL(userId);
1964 
1965             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1966 
1967             ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1968 
1969             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1970                 ps.deleteDynamicWithId(
1971                         Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
1972             }
1973 
1974             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
1975             ps.adjustRanks();
1976         }
1977         packageShortcutsChanged(packageName, userId);
1978 
1979         verifyStates();
1980     }
1981 
1982     @Override
removeAllDynamicShortcuts(String packageName, @UserIdInt int userId)1983     public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
1984         verifyCaller(packageName, userId);
1985 
1986         synchronized (mLock) {
1987             throwIfUserLockedL(userId);
1988 
1989             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
1990             ps.deleteAllDynamicShortcuts();
1991         }
1992         packageShortcutsChanged(packageName, userId);
1993 
1994         verifyStates();
1995     }
1996 
1997     @Override
getDynamicShortcuts(String packageName, @UserIdInt int userId)1998     public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
1999             @UserIdInt int userId) {
2000         verifyCaller(packageName, userId);
2001 
2002         synchronized (mLock) {
2003             throwIfUserLockedL(userId);
2004 
2005             return getShortcutsWithQueryLocked(
2006                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
2007                     ShortcutInfo::isDynamic);
2008         }
2009     }
2010 
2011     @Override
getManifestShortcuts(String packageName, @UserIdInt int userId)2012     public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
2013             @UserIdInt int userId) {
2014         verifyCaller(packageName, userId);
2015 
2016         synchronized (mLock) {
2017             throwIfUserLockedL(userId);
2018 
2019             return getShortcutsWithQueryLocked(
2020                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
2021                     ShortcutInfo::isManifestShortcut);
2022         }
2023     }
2024 
2025     @Override
getPinnedShortcuts(String packageName, @UserIdInt int userId)2026     public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
2027             @UserIdInt int userId) {
2028         verifyCaller(packageName, userId);
2029 
2030         synchronized (mLock) {
2031             throwIfUserLockedL(userId);
2032 
2033             return getShortcutsWithQueryLocked(
2034                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
2035                     ShortcutInfo::isPinned);
2036         }
2037     }
2038 
getShortcutsWithQueryLocked(@onNull String packageName, @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query)2039     private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
2040             @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
2041 
2042         final ArrayList<ShortcutInfo> ret = new ArrayList<>();
2043 
2044         final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
2045         ps.findAll(ret, query, cloneFlags);
2046 
2047         return new ParceledListSlice<>(setReturnedByServer(ret));
2048     }
2049 
2050     @Override
getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId)2051     public int getMaxShortcutCountPerActivity(String packageName, @UserIdInt int userId)
2052             throws RemoteException {
2053         verifyCaller(packageName, userId);
2054 
2055         return mMaxShortcuts;
2056     }
2057 
2058     @Override
getRemainingCallCount(String packageName, @UserIdInt int userId)2059     public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
2060         verifyCaller(packageName, userId);
2061 
2062         synchronized (mLock) {
2063             throwIfUserLockedL(userId);
2064 
2065             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
2066             return mMaxUpdatesPerInterval - ps.getApiCallCount();
2067         }
2068     }
2069 
2070     @Override
getRateLimitResetTime(String packageName, @UserIdInt int userId)2071     public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
2072         verifyCaller(packageName, userId);
2073 
2074         synchronized (mLock) {
2075             throwIfUserLockedL(userId);
2076 
2077             return getNextResetTimeLocked();
2078         }
2079     }
2080 
2081     @Override
getIconMaxDimensions(String packageName, int userId)2082     public int getIconMaxDimensions(String packageName, int userId) {
2083         verifyCaller(packageName, userId);
2084 
2085         synchronized (mLock) {
2086             return mMaxIconDimension;
2087         }
2088     }
2089 
2090     @Override
reportShortcutUsed(String packageName, String shortcutId, int userId)2091     public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
2092         verifyCaller(packageName, userId);
2093 
2094         Preconditions.checkNotNull(shortcutId);
2095 
2096         if (DEBUG) {
2097             Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
2098                     shortcutId, packageName, userId));
2099         }
2100 
2101         synchronized (mLock) {
2102             throwIfUserLockedL(userId);
2103 
2104             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
2105 
2106             if (ps.findShortcutById(shortcutId) == null) {
2107                 Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
2108                         packageName, shortcutId));
2109                 return;
2110             }
2111         }
2112 
2113         final long token = injectClearCallingIdentity();
2114         try {
2115             mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
2116         } finally {
2117             injectRestoreCallingIdentity(token);
2118         }
2119     }
2120 
2121     @Override
isRequestPinItemSupported(int callingUserId, int requestType)2122     public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
2123         final long token = injectClearCallingIdentity();
2124         try {
2125             return mShortcutRequestPinProcessor
2126                     .isRequestPinItemSupported(callingUserId, requestType);
2127         } finally {
2128             injectRestoreCallingIdentity(token);
2129         }
2130     }
2131 
2132     /**
2133      * Reset all throttling, for developer options and command line.  Only system/shell can call
2134      * it.
2135      */
2136     @Override
resetThrottling()2137     public void resetThrottling() {
2138         enforceSystemOrShell();
2139 
2140         resetThrottlingInner(getCallingUserId());
2141     }
2142 
resetThrottlingInner(@serIdInt int userId)2143     void resetThrottlingInner(@UserIdInt int userId) {
2144         synchronized (mLock) {
2145             if (!isUserUnlockedL(userId)) {
2146                 Log.w(TAG, "User " + userId + " is locked or not running");
2147                 return;
2148             }
2149 
2150             getUserShortcutsLocked(userId).resetThrottling();
2151         }
2152         scheduleSaveUser(userId);
2153         Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId);
2154     }
2155 
resetAllThrottlingInner()2156     void resetAllThrottlingInner() {
2157         synchronized (mLock) {
2158             mRawLastResetTime = injectCurrentTimeMillis();
2159         }
2160         scheduleSaveBaseState();
2161         Slog.i(TAG, "ShortcutManager: throttling counter reset for all users");
2162     }
2163 
2164     @Override
onApplicationActive(String packageName, int userId)2165     public void onApplicationActive(String packageName, int userId) {
2166         if (DEBUG) {
2167             Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userid=" + userId);
2168         }
2169         enforceResetThrottlingPermission();
2170 
2171         synchronized (mLock) {
2172             if (!isUserUnlockedL(userId)) {
2173                 // This is called by system UI, so no need to throw.  Just ignore.
2174                 return;
2175             }
2176 
2177             getPackageShortcutsLocked(packageName, userId)
2178                     .resetRateLimitingForCommandLineNoSaving();
2179             saveUserLocked(userId);
2180         }
2181     }
2182 
2183     // We override this method in unit tests to do a simpler check.
hasShortcutHostPermission(@onNull String callingPackage, int userId)2184     boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
2185         final long start = injectElapsedRealtime();
2186         try {
2187             return hasShortcutHostPermissionInner(callingPackage, userId);
2188         } finally {
2189             logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
2190         }
2191     }
2192 
2193     // This method is extracted so we can directly call this method from unit tests,
2194     // even when hasShortcutPermission() is overridden.
2195     @VisibleForTesting
hasShortcutHostPermissionInner(@onNull String packageName, int userId)2196     boolean hasShortcutHostPermissionInner(@NonNull String packageName, int userId) {
2197         synchronized (mLock) {
2198             throwIfUserLockedL(userId);
2199 
2200             final ShortcutUser user = getUserShortcutsLocked(userId);
2201 
2202             // Always trust the cached component.
2203             final ComponentName cached = user.getCachedLauncher();
2204             if (cached != null) {
2205                 if (cached.getPackageName().equals(packageName)) {
2206                     return true;
2207                 }
2208             }
2209             // If the cached one doesn't match, then go ahead
2210 
2211             final ComponentName detected = getDefaultLauncher(userId);
2212 
2213             // Update the cache.
2214             user.setLauncher(detected);
2215             if (detected != null) {
2216                 if (DEBUG) {
2217                     Slog.v(TAG, "Detected launcher: " + detected);
2218                 }
2219                 return detected.getPackageName().equals(packageName);
2220             } else {
2221                 // Default launcher not found.
2222                 return false;
2223             }
2224         }
2225     }
2226 
2227     @Nullable
getDefaultLauncher(@serIdInt int userId)2228     ComponentName getDefaultLauncher(@UserIdInt int userId) {
2229         final long start = injectElapsedRealtime();
2230         final long token = injectClearCallingIdentity();
2231         try {
2232             synchronized (mLock) {
2233                 throwIfUserLockedL(userId);
2234 
2235                 final ShortcutUser user = getUserShortcutsLocked(userId);
2236 
2237                 final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
2238 
2239                 // Default launcher from package manager.
2240                 final long startGetHomeActivitiesAsUser = injectElapsedRealtime();
2241                 final ComponentName defaultLauncher = mPackageManagerInternal
2242                         .getHomeActivitiesAsUser(allHomeCandidates, userId);
2243                 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
2244 
2245                 ComponentName detected = null;
2246                 if (defaultLauncher != null) {
2247                     detected = defaultLauncher;
2248                     if (DEBUG) {
2249                         Slog.v(TAG, "Default launcher from PM: " + detected);
2250                     }
2251                 } else {
2252                     detected = user.getLastKnownLauncher();
2253 
2254                     if (detected != null) {
2255                         if (injectIsActivityEnabledAndExported(detected, userId)) {
2256                             if (DEBUG) {
2257                                 Slog.v(TAG, "Cached launcher: " + detected);
2258                             }
2259                         } else {
2260                             Slog.w(TAG, "Cached launcher " + detected + " no longer exists");
2261                             detected = null;
2262                             user.clearLauncher();
2263                         }
2264                     }
2265                 }
2266 
2267                 if (detected == null) {
2268                     // If we reach here, that means it's the first check since the user was created,
2269                     // and there's already multiple launchers and there's no default set.
2270                     // Find the system one with the highest priority.
2271                     // (We need to check the priority too because of FallbackHome in Settings.)
2272                     // If there's no system launcher yet, then no one can access shortcuts, until
2273                     // the user explicitly
2274                     final int size = allHomeCandidates.size();
2275 
2276                     int lastPriority = Integer.MIN_VALUE;
2277                     for (int i = 0; i < size; i++) {
2278                         final ResolveInfo ri = allHomeCandidates.get(i);
2279                         if (!ri.activityInfo.applicationInfo.isSystemApp()) {
2280                             continue;
2281                         }
2282                         if (DEBUG) {
2283                             Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
2284                                     ri.activityInfo.getComponentName(), ri.priority));
2285                         }
2286                         if (ri.priority < lastPriority) {
2287                             continue;
2288                         }
2289                         detected = ri.activityInfo.getComponentName();
2290                         lastPriority = ri.priority;
2291                     }
2292                 }
2293                 return detected;
2294             }
2295         } finally {
2296             injectRestoreCallingIdentity(token);
2297             logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start);
2298         }
2299     }
2300 
2301     // === House keeping ===
2302 
cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId, boolean appStillExists)2303     private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
2304             boolean appStillExists) {
2305         synchronized (mLock) {
2306             forEachLoadedUserLocked(user ->
2307                     cleanUpPackageLocked(packageName, user.getUserId(), packageUserId,
2308                             appStillExists));
2309         }
2310     }
2311 
2312     /**
2313      * Remove all the information associated with a package.  This will really remove all the
2314      * information, including the restore information (i.e. it'll remove packages even if they're
2315      * shadow).
2316      *
2317      * This is called when an app is uninstalled, or an app gets "clear data"ed.
2318      */
2319     @VisibleForTesting
cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId, boolean appStillExists)2320     void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
2321             boolean appStillExists) {
2322         final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
2323 
2324         final ShortcutUser user = getUserShortcutsLocked(owningUserId);
2325         boolean doNotify = false;
2326 
2327         // First, remove the package from the package list (if the package is a publisher).
2328         if (packageUserId == owningUserId) {
2329             if (user.removePackage(packageName) != null) {
2330                 doNotify = true;
2331             }
2332         }
2333 
2334         // Also remove from the launcher list (if the package is a launcher).
2335         user.removeLauncher(packageUserId, packageName);
2336 
2337         // Then remove pinned shortcuts from all launchers.
2338         user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId));
2339 
2340         // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous
2341         // step.  Remove them too.
2342         user.forAllPackages(p -> p.refreshPinnedFlags());
2343 
2344         scheduleSaveUser(owningUserId);
2345 
2346         if (doNotify) {
2347             notifyListeners(packageName, owningUserId);
2348         }
2349 
2350         // If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts.
2351         if (appStillExists && (packageUserId == owningUserId)) {
2352             // This will do the notification and save when needed, so do it after the above
2353             // notifyListeners.
2354             user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
2355         }
2356 
2357         if (!wasUserLoaded) {
2358             // Note this will execute the scheduled save.
2359             unloadUserLocked(owningUserId);
2360         }
2361     }
2362 
2363     /**
2364      * Entry point from {@link LauncherApps}.
2365      */
2366     private class LocalService extends ShortcutServiceInternal {
2367 
2368         @Override
getShortcuts(int launcherUserId, @NonNull String callingPackage, long changedSince, @Nullable String packageName, @Nullable List<String> shortcutIds, @Nullable ComponentName componentName, int queryFlags, int userId)2369         public List<ShortcutInfo> getShortcuts(int launcherUserId,
2370                 @NonNull String callingPackage, long changedSince,
2371                 @Nullable String packageName, @Nullable List<String> shortcutIds,
2372                 @Nullable ComponentName componentName,
2373                 int queryFlags, int userId) {
2374             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
2375 
2376             final boolean cloneKeyFieldOnly =
2377                     ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
2378             final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO
2379                     : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
2380             if (packageName == null) {
2381                 shortcutIds = null; // LauncherAppsService already threw for it though.
2382             }
2383 
2384             synchronized (mLock) {
2385                 throwIfUserLockedL(userId);
2386                 throwIfUserLockedL(launcherUserId);
2387 
2388                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2389                         .attemptToRestoreIfNeededAndSave();
2390 
2391                 if (packageName != null) {
2392                     getShortcutsInnerLocked(launcherUserId,
2393                             callingPackage, packageName, shortcutIds, changedSince,
2394                             componentName, queryFlags, userId, ret, cloneFlag);
2395                 } else {
2396                     final List<String> shortcutIdsF = shortcutIds;
2397                     getUserShortcutsLocked(userId).forAllPackages(p -> {
2398                         getShortcutsInnerLocked(launcherUserId,
2399                                 callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
2400                                 componentName, queryFlags, userId, ret, cloneFlag);
2401                     });
2402                 }
2403             }
2404             return setReturnedByServer(ret);
2405         }
2406 
getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage, @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince, @Nullable ComponentName componentName, int queryFlags, int userId, ArrayList<ShortcutInfo> ret, int cloneFlag)2407         private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
2408                 @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
2409                 @Nullable ComponentName componentName, int queryFlags,
2410                 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
2411             final ArraySet<String> ids = shortcutIds == null ? null
2412                     : new ArraySet<>(shortcutIds);
2413 
2414             final ShortcutPackage p = getUserShortcutsLocked(userId)
2415                     .getPackageShortcutsIfExists(packageName);
2416             if (p == null) {
2417                 return; // No need to instantiate ShortcutPackage.
2418             }
2419 
2420             p.findAll(ret,
2421                     (ShortcutInfo si) -> {
2422                         if (si.getLastChangedTimestamp() < changedSince) {
2423                             return false;
2424                         }
2425                         if (ids != null && !ids.contains(si.getId())) {
2426                             return false;
2427                         }
2428                         if (componentName != null) {
2429                             if (si.getActivity() != null
2430                                     && !si.getActivity().equals(componentName)) {
2431                                 return false;
2432                             }
2433                         }
2434                         if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
2435                                 && si.isDynamic()) {
2436                             return true;
2437                         }
2438                         if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
2439                                 && si.isPinned()) {
2440                             return true;
2441                         }
2442                         if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
2443                                 && si.isManifestShortcut()) {
2444                             return true;
2445                         }
2446                         return false;
2447                     }, cloneFlag, callingPackage, launcherUserId);
2448         }
2449 
2450         @Override
isPinnedByCaller(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)2451         public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
2452                 @NonNull String packageName, @NonNull String shortcutId, int userId) {
2453             Preconditions.checkStringNotEmpty(packageName, "packageName");
2454             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
2455 
2456             synchronized (mLock) {
2457                 throwIfUserLockedL(userId);
2458                 throwIfUserLockedL(launcherUserId);
2459 
2460                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2461                         .attemptToRestoreIfNeededAndSave();
2462 
2463                 final ShortcutInfo si = getShortcutInfoLocked(
2464                         launcherUserId, callingPackage, packageName, shortcutId, userId);
2465                 return si != null && si.isPinned();
2466             }
2467         }
2468 
getShortcutInfoLocked( int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)2469         private ShortcutInfo getShortcutInfoLocked(
2470                 int launcherUserId, @NonNull String callingPackage,
2471                 @NonNull String packageName, @NonNull String shortcutId, int userId) {
2472             Preconditions.checkStringNotEmpty(packageName, "packageName");
2473             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
2474 
2475             throwIfUserLockedL(userId);
2476             throwIfUserLockedL(launcherUserId);
2477 
2478             final ShortcutPackage p = getUserShortcutsLocked(userId)
2479                     .getPackageShortcutsIfExists(packageName);
2480             if (p == null) {
2481                 return null;
2482             }
2483 
2484             final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
2485             p.findAll(list,
2486                     (ShortcutInfo si) -> shortcutId.equals(si.getId()),
2487                     /* clone flags=*/ 0, callingPackage, launcherUserId);
2488             return list.size() == 0 ? null : list.get(0);
2489         }
2490 
2491         @Override
pinShortcuts(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull List<String> shortcutIds, int userId)2492         public void pinShortcuts(int launcherUserId,
2493                 @NonNull String callingPackage, @NonNull String packageName,
2494                 @NonNull List<String> shortcutIds, int userId) {
2495             // Calling permission must be checked by LauncherAppsImpl.
2496             Preconditions.checkStringNotEmpty(packageName, "packageName");
2497             Preconditions.checkNotNull(shortcutIds, "shortcutIds");
2498 
2499             synchronized (mLock) {
2500                 throwIfUserLockedL(userId);
2501                 throwIfUserLockedL(launcherUserId);
2502 
2503                 final ShortcutLauncher launcher =
2504                         getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
2505                 launcher.attemptToRestoreIfNeededAndSave();
2506 
2507                 launcher.pinShortcuts(userId, packageName, shortcutIds);
2508             }
2509             packageShortcutsChanged(packageName, userId);
2510 
2511             verifyStates();
2512         }
2513 
2514         @Override
createShortcutIntents(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)2515         public Intent[] createShortcutIntents(int launcherUserId,
2516                 @NonNull String callingPackage,
2517                 @NonNull String packageName, @NonNull String shortcutId, int userId) {
2518             // Calling permission must be checked by LauncherAppsImpl.
2519             Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
2520             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
2521 
2522             synchronized (mLock) {
2523                 throwIfUserLockedL(userId);
2524                 throwIfUserLockedL(launcherUserId);
2525 
2526                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2527                         .attemptToRestoreIfNeededAndSave();
2528 
2529                 // Make sure the shortcut is actually visible to the launcher.
2530                 final ShortcutInfo si = getShortcutInfoLocked(
2531                         launcherUserId, callingPackage, packageName, shortcutId, userId);
2532                 // "si == null" should suffice here, but check the flags too just to make sure.
2533                 if (si == null || !si.isEnabled() || !si.isAlive()) {
2534                     Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled");
2535                     return null;
2536                 }
2537                 return si.getIntents();
2538             }
2539         }
2540 
2541         @Override
addListener(@onNull ShortcutChangeListener listener)2542         public void addListener(@NonNull ShortcutChangeListener listener) {
2543             synchronized (mLock) {
2544                 mListeners.add(Preconditions.checkNotNull(listener));
2545             }
2546         }
2547 
2548         @Override
getShortcutIconResId(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)2549         public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
2550                 @NonNull String packageName, @NonNull String shortcutId, int userId) {
2551             Preconditions.checkNotNull(callingPackage, "callingPackage");
2552             Preconditions.checkNotNull(packageName, "packageName");
2553             Preconditions.checkNotNull(shortcutId, "shortcutId");
2554 
2555             synchronized (mLock) {
2556                 throwIfUserLockedL(userId);
2557                 throwIfUserLockedL(launcherUserId);
2558 
2559                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2560                         .attemptToRestoreIfNeededAndSave();
2561 
2562                 final ShortcutPackage p = getUserShortcutsLocked(userId)
2563                         .getPackageShortcutsIfExists(packageName);
2564                 if (p == null) {
2565                     return 0;
2566                 }
2567 
2568                 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
2569                 return (shortcutInfo != null && shortcutInfo.hasIconResource())
2570                         ? shortcutInfo.getIconResourceId() : 0;
2571             }
2572         }
2573 
2574         @Override
getShortcutIconFd(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId)2575         public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
2576                 @NonNull String callingPackage, @NonNull String packageName,
2577                 @NonNull String shortcutId, int userId) {
2578             Preconditions.checkNotNull(callingPackage, "callingPackage");
2579             Preconditions.checkNotNull(packageName, "packageName");
2580             Preconditions.checkNotNull(shortcutId, "shortcutId");
2581 
2582             synchronized (mLock) {
2583                 throwIfUserLockedL(userId);
2584                 throwIfUserLockedL(launcherUserId);
2585 
2586                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2587                         .attemptToRestoreIfNeededAndSave();
2588 
2589                 final ShortcutPackage p = getUserShortcutsLocked(userId)
2590                         .getPackageShortcutsIfExists(packageName);
2591                 if (p == null) {
2592                     return null;
2593                 }
2594 
2595                 final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
2596                 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
2597                     return null;
2598                 }
2599                 final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo);
2600                 if (path == null) {
2601                     Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
2602                     return null;
2603                 }
2604                 try {
2605                     return ParcelFileDescriptor.open(
2606                             new File(path),
2607                             ParcelFileDescriptor.MODE_READ_ONLY);
2608                 } catch (FileNotFoundException e) {
2609                     Slog.e(TAG, "Icon file not found: " + path);
2610                     return null;
2611                 }
2612             }
2613         }
2614 
2615         @Override
hasShortcutHostPermission(int launcherUserId, @NonNull String callingPackage)2616         public boolean hasShortcutHostPermission(int launcherUserId,
2617                 @NonNull String callingPackage) {
2618             return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
2619         }
2620 
2621         @Override
requestPinAppWidget(@onNull String callingPackage, @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras, @Nullable IntentSender resultIntent, int userId)2622         public boolean requestPinAppWidget(@NonNull String callingPackage,
2623                 @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
2624                 @Nullable IntentSender resultIntent, int userId) {
2625             Preconditions.checkNotNull(appWidget);
2626             return requestPinItem(callingPackage, userId, null, appWidget, extras, resultIntent);
2627         }
2628 
2629         @Override
isRequestPinItemSupported(int callingUserId, int requestType)2630         public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
2631             return ShortcutService.this.isRequestPinItemSupported(callingUserId, requestType);
2632         }
2633     }
2634 
2635     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
2636         @Override
2637         public void onReceive(Context context, Intent intent) {
2638             if (!mBootCompleted.get()) {
2639                 return; // Boot not completed, ignore the broadcast.
2640             }
2641             try {
2642                 if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
2643                     handleLocaleChanged();
2644                 }
2645             } catch (Exception e) {
2646                 wtf("Exception in mReceiver.onReceive", e);
2647             }
2648         }
2649     };
2650 
handleLocaleChanged()2651     void handleLocaleChanged() {
2652         if (DEBUG) {
2653             Slog.d(TAG, "handleLocaleChanged");
2654         }
2655         scheduleSaveBaseState();
2656 
2657         synchronized (mLock) {
2658             final long token = injectClearCallingIdentity();
2659             try {
2660                 forEachLoadedUserLocked(user -> user.detectLocaleChange());
2661             } finally {
2662                 injectRestoreCallingIdentity(token);
2663             }
2664         }
2665     }
2666 
2667     /**
2668      * Package event callbacks.
2669      */
2670     @VisibleForTesting
2671     final BroadcastReceiver mPackageMonitor = new BroadcastReceiver() {
2672         @Override
2673         public void onReceive(Context context, Intent intent) {
2674             final int userId  = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
2675             if (userId == UserHandle.USER_NULL) {
2676                 Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent);
2677                 return;
2678             }
2679 
2680             final String action = intent.getAction();
2681 
2682             // This is normally called on Handler, so clearCallingIdentity() isn't needed,
2683             // but we still check it in unit tests.
2684             final long token = injectClearCallingIdentity();
2685             try {
2686                 synchronized (mLock) {
2687                     if (!isUserUnlockedL(userId)) {
2688                         if (DEBUG) {
2689                             Slog.d(TAG, "Ignoring package broadcast " + action
2690                                     + " for locked/stopped user " + userId);
2691                         }
2692                         return;
2693                     }
2694 
2695                     // Whenever we get one of those package broadcasts, or get
2696                     // ACTION_PREFERRED_ACTIVITY_CHANGED, we purge the default launcher cache.
2697                     final ShortcutUser user = getUserShortcutsLocked(userId);
2698                     user.clearLauncher();
2699                 }
2700                 if (Intent.ACTION_PREFERRED_ACTIVITY_CHANGED.equals(action)) {
2701                     // Nothing farther to do.
2702                     return;
2703                 }
2704 
2705                 final Uri intentUri = intent.getData();
2706                 final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart()
2707                         : null;
2708                 if (packageName == null) {
2709                     Slog.w(TAG, "Intent broadcast does not contain package name: " + intent);
2710                     return;
2711                 }
2712 
2713                 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
2714 
2715                 switch (action) {
2716                     case Intent.ACTION_PACKAGE_ADDED:
2717                         if (replacing) {
2718                             handlePackageUpdateFinished(packageName, userId);
2719                         } else {
2720                             handlePackageAdded(packageName, userId);
2721                         }
2722                         break;
2723                     case Intent.ACTION_PACKAGE_REMOVED:
2724                         if (!replacing) {
2725                             handlePackageRemoved(packageName, userId);
2726                         }
2727                         break;
2728                     case Intent.ACTION_PACKAGE_CHANGED:
2729                         handlePackageChanged(packageName, userId);
2730 
2731                         break;
2732                     case Intent.ACTION_PACKAGE_DATA_CLEARED:
2733                         handlePackageDataCleared(packageName, userId);
2734                         break;
2735                 }
2736             } catch (Exception e) {
2737                 wtf("Exception in mPackageMonitor.onReceive", e);
2738             } finally {
2739                 injectRestoreCallingIdentity(token);
2740             }
2741         }
2742     };
2743 
2744     /**
2745      * Called when a user is unlocked.
2746      * - Check all known packages still exist, and otherwise perform cleanup.
2747      * - If a package still exists, check the version code.  If it's been updated, may need to
2748      * update timestamps of its shortcuts.
2749      */
2750     @VisibleForTesting
checkPackageChanges(@serIdInt int ownerUserId)2751     void checkPackageChanges(@UserIdInt int ownerUserId) {
2752         if (DEBUG) {
2753             Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
2754         }
2755         if (injectIsSafeModeEnabled()) {
2756             Slog.i(TAG, "Safe mode, skipping checkPackageChanges()");
2757             return;
2758         }
2759 
2760         final long start = injectElapsedRealtime();
2761         try {
2762             final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
2763 
2764             synchronized (mLock) {
2765                 final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
2766 
2767                 // Find packages that have been uninstalled.
2768                 user.forAllPackageItems(spi -> {
2769                     if (spi.getPackageInfo().isShadow()) {
2770                         return; // Don't delete shadow information.
2771                     }
2772                     if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
2773                         if (DEBUG) {
2774                             Slog.d(TAG, "Uninstalled: " + spi.getPackageName()
2775                                     + " user " + spi.getPackageUserId());
2776                         }
2777                         gonePackages.add(PackageWithUser.of(spi));
2778                     }
2779                 });
2780                 if (gonePackages.size() > 0) {
2781                     for (int i = gonePackages.size() - 1; i >= 0; i--) {
2782                         final PackageWithUser pu = gonePackages.get(i);
2783                         cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId,
2784                                 /* appStillExists = */ false);
2785                     }
2786                 }
2787 
2788                 rescanUpdatedPackagesLocked(ownerUserId, user.getLastAppScanTime());
2789             }
2790         } finally {
2791             logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
2792         }
2793         verifyStates();
2794     }
2795 
rescanUpdatedPackagesLocked(@serIdInt int userId, long lastScanTime)2796     private void rescanUpdatedPackagesLocked(@UserIdInt int userId, long lastScanTime) {
2797         final ShortcutUser user = getUserShortcutsLocked(userId);
2798 
2799         // Note after each OTA, we'll need to rescan all system apps, as their lastUpdateTime
2800         // is not reliable.
2801         final long now = injectCurrentTimeMillis();
2802         final boolean afterOta =
2803                 !injectBuildFingerprint().equals(user.getLastAppScanOsFingerprint());
2804 
2805         // Then for each installed app, publish manifest shortcuts when needed.
2806         forUpdatedPackages(userId, lastScanTime, afterOta, ai -> {
2807             user.attemptToRestoreIfNeededAndSave(this, ai.packageName, userId);
2808 
2809             user.rescanPackageIfNeeded(ai.packageName, /* forceRescan= */ true);
2810         });
2811 
2812         // Write the time just before the scan, because there may be apps that have just
2813         // been updated, and we want to catch them in the next time.
2814         user.setLastAppScanTime(now);
2815         user.setLastAppScanOsFingerprint(injectBuildFingerprint());
2816         scheduleSaveUser(userId);
2817     }
2818 
handlePackageAdded(String packageName, @UserIdInt int userId)2819     private void handlePackageAdded(String packageName, @UserIdInt int userId) {
2820         if (DEBUG) {
2821             Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
2822         }
2823         synchronized (mLock) {
2824             final ShortcutUser user = getUserShortcutsLocked(userId);
2825             user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
2826             user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
2827         }
2828         verifyStates();
2829     }
2830 
handlePackageUpdateFinished(String packageName, @UserIdInt int userId)2831     private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
2832         if (DEBUG) {
2833             Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
2834                     packageName, userId));
2835         }
2836         synchronized (mLock) {
2837             final ShortcutUser user = getUserShortcutsLocked(userId);
2838             user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
2839 
2840             if (isPackageInstalled(packageName, userId)) {
2841                 user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
2842             }
2843         }
2844         verifyStates();
2845     }
2846 
handlePackageRemoved(String packageName, @UserIdInt int packageUserId)2847     private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
2848         if (DEBUG) {
2849             Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
2850                     packageUserId));
2851         }
2852         cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false);
2853 
2854         verifyStates();
2855     }
2856 
handlePackageDataCleared(String packageName, int packageUserId)2857     private void handlePackageDataCleared(String packageName, int packageUserId) {
2858         if (DEBUG) {
2859             Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
2860                     packageUserId));
2861         }
2862         cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true);
2863 
2864         verifyStates();
2865     }
2866 
handlePackageChanged(String packageName, int packageUserId)2867     private void handlePackageChanged(String packageName, int packageUserId) {
2868         if (!isPackageInstalled(packageName, packageUserId)) {
2869             // Probably disabled, which is the same thing as uninstalled.
2870             handlePackageRemoved(packageName, packageUserId);
2871             return;
2872         }
2873         if (DEBUG) {
2874             Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName,
2875                     packageUserId));
2876         }
2877 
2878         // Activities may be disabled or enabled.  Just rescan the package.
2879         synchronized (mLock) {
2880             final ShortcutUser user = getUserShortcutsLocked(packageUserId);
2881 
2882             user.rescanPackageIfNeeded(packageName, /* forceRescan=*/ true);
2883         }
2884 
2885         verifyStates();
2886     }
2887 
2888     // === PackageManager interaction ===
2889 
2890     /**
2891      * Returns {@link PackageInfo} unless it's uninstalled or disabled.
2892      */
2893     @Nullable
getPackageInfoWithSignatures(String packageName, @UserIdInt int userId)2894     final PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
2895         return getPackageInfo(packageName, userId, true);
2896     }
2897 
2898     /**
2899      * Returns {@link PackageInfo} unless it's uninstalled or disabled.
2900      */
2901     @Nullable
getPackageInfo(String packageName, @UserIdInt int userId)2902     final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
2903         return getPackageInfo(packageName, userId, false);
2904     }
2905 
injectGetPackageUid(@onNull String packageName, @UserIdInt int userId)2906     int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
2907         final long token = injectClearCallingIdentity();
2908         try {
2909             return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS, userId);
2910         } catch (RemoteException e) {
2911             // Shouldn't happen.
2912             Slog.wtf(TAG, "RemoteException", e);
2913             return -1;
2914         } finally {
2915             injectRestoreCallingIdentity(token);
2916         }
2917     }
2918 
2919     /**
2920      * Returns {@link PackageInfo} unless it's uninstalled or disabled.
2921      */
2922     @Nullable
2923     @VisibleForTesting
getPackageInfo(String packageName, @UserIdInt int userId, boolean getSignatures)2924     final PackageInfo getPackageInfo(String packageName, @UserIdInt int userId,
2925             boolean getSignatures) {
2926         return isInstalledOrNull(injectPackageInfoWithUninstalled(
2927                 packageName, userId, getSignatures));
2928     }
2929 
2930     /**
2931      * Do not use directly; this returns uninstalled packages too.
2932      */
2933     @Nullable
2934     @VisibleForTesting
injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId, boolean getSignatures)2935     PackageInfo injectPackageInfoWithUninstalled(String packageName, @UserIdInt int userId,
2936             boolean getSignatures) {
2937         final long start = injectElapsedRealtime();
2938         final long token = injectClearCallingIdentity();
2939         try {
2940             return mIPackageManager.getPackageInfo(
2941                     packageName, PACKAGE_MATCH_FLAGS
2942                             | (getSignatures ? PackageManager.GET_SIGNATURES : 0), userId);
2943         } catch (RemoteException e) {
2944             // Shouldn't happen.
2945             Slog.wtf(TAG, "RemoteException", e);
2946             return null;
2947         } finally {
2948             injectRestoreCallingIdentity(token);
2949 
2950             logDurationStat(
2951                     (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
2952                     start);
2953         }
2954     }
2955 
2956     /**
2957      * Returns {@link ApplicationInfo} unless it's uninstalled or disabled.
2958      */
2959     @Nullable
2960     @VisibleForTesting
getApplicationInfo(String packageName, @UserIdInt int userId)2961     final ApplicationInfo getApplicationInfo(String packageName, @UserIdInt int userId) {
2962         return isInstalledOrNull(injectApplicationInfoWithUninstalled(packageName, userId));
2963     }
2964 
2965     /**
2966      * Do not use directly; this returns uninstalled packages too.
2967      */
2968     @Nullable
2969     @VisibleForTesting
injectApplicationInfoWithUninstalled( String packageName, @UserIdInt int userId)2970     ApplicationInfo injectApplicationInfoWithUninstalled(
2971             String packageName, @UserIdInt int userId) {
2972         final long start = injectElapsedRealtime();
2973         final long token = injectClearCallingIdentity();
2974         try {
2975             return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
2976         } catch (RemoteException e) {
2977             // Shouldn't happen.
2978             Slog.wtf(TAG, "RemoteException", e);
2979             return null;
2980         } finally {
2981             injectRestoreCallingIdentity(token);
2982 
2983             logDurationStat(Stats.GET_APPLICATION_INFO, start);
2984         }
2985     }
2986 
2987     /**
2988      * Returns {@link ActivityInfo} with its metadata unless it's uninstalled or disabled.
2989      */
2990     @Nullable
getActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId)2991     final ActivityInfo getActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) {
2992         return isInstalledOrNull(injectGetActivityInfoWithMetadataWithUninstalled(
2993                 activity, userId));
2994     }
2995 
2996     /**
2997      * Do not use directly; this returns uninstalled packages too.
2998      */
2999     @Nullable
3000     @VisibleForTesting
injectGetActivityInfoWithMetadataWithUninstalled( ComponentName activity, @UserIdInt int userId)3001     ActivityInfo injectGetActivityInfoWithMetadataWithUninstalled(
3002             ComponentName activity, @UserIdInt int userId) {
3003         final long start = injectElapsedRealtime();
3004         final long token = injectClearCallingIdentity();
3005         try {
3006             return mIPackageManager.getActivityInfo(activity,
3007                     (PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA), userId);
3008         } catch (RemoteException e) {
3009             // Shouldn't happen.
3010             Slog.wtf(TAG, "RemoteException", e);
3011             return null;
3012         } finally {
3013             injectRestoreCallingIdentity(token);
3014 
3015             logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start);
3016         }
3017     }
3018 
3019     /**
3020      * Return all installed and enabled packages.
3021      */
3022     @NonNull
3023     @VisibleForTesting
getInstalledPackages(@serIdInt int userId)3024     final List<PackageInfo> getInstalledPackages(@UserIdInt int userId) {
3025         final long start = injectElapsedRealtime();
3026         final long token = injectClearCallingIdentity();
3027         try {
3028             final List<PackageInfo> all = injectGetPackagesWithUninstalled(userId);
3029 
3030             all.removeIf(PACKAGE_NOT_INSTALLED);
3031 
3032             return all;
3033         } catch (RemoteException e) {
3034             // Shouldn't happen.
3035             Slog.wtf(TAG, "RemoteException", e);
3036             return null;
3037         } finally {
3038             injectRestoreCallingIdentity(token);
3039 
3040             logDurationStat(Stats.GET_INSTALLED_PACKAGES, start);
3041         }
3042     }
3043 
3044     /**
3045      * Do not use directly; this returns uninstalled packages too.
3046      */
3047     @NonNull
3048     @VisibleForTesting
injectGetPackagesWithUninstalled(@serIdInt int userId)3049     List<PackageInfo> injectGetPackagesWithUninstalled(@UserIdInt int userId)
3050             throws RemoteException {
3051         final ParceledListSlice<PackageInfo> parceledList =
3052                 mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId);
3053         if (parceledList == null) {
3054             return Collections.emptyList();
3055         }
3056         return parceledList.getList();
3057     }
3058 
forUpdatedPackages(@serIdInt int userId, long lastScanTime, boolean afterOta, Consumer<ApplicationInfo> callback)3059     private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime, boolean afterOta,
3060             Consumer<ApplicationInfo> callback) {
3061         if (DEBUG) {
3062             Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime
3063                     + " afterOta=" + afterOta);
3064         }
3065         final List<PackageInfo> list = getInstalledPackages(userId);
3066         for (int i = list.size() - 1; i >= 0; i--) {
3067             final PackageInfo pi = list.get(i);
3068 
3069             // If the package has been updated since the last scan time, then scan it.
3070             // Also if it's right after an OTA, always re-scan all apps anyway, since the
3071             // shortcut parser might have changed.
3072             if (afterOta || (pi.lastUpdateTime >= lastScanTime)) {
3073                 if (DEBUG) {
3074                     Slog.d(TAG, "Found updated package " + pi.packageName
3075                             + " updateTime=" + pi.lastUpdateTime);
3076                 }
3077                 callback.accept(pi.applicationInfo);
3078             }
3079         }
3080     }
3081 
isApplicationFlagSet(@onNull String packageName, int userId, int flags)3082     private boolean isApplicationFlagSet(@NonNull String packageName, int userId, int flags) {
3083         final ApplicationInfo ai = injectApplicationInfoWithUninstalled(packageName, userId);
3084         return (ai != null) && ((ai.flags & flags) == flags);
3085     }
3086 
isInstalled(@ullable ApplicationInfo ai)3087     private static boolean isInstalled(@Nullable ApplicationInfo ai) {
3088         return (ai != null) && ai.enabled && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
3089     }
3090 
isEphemeralApp(@ullable ApplicationInfo ai)3091     private static boolean isEphemeralApp(@Nullable ApplicationInfo ai) {
3092         return (ai != null) && ai.isInstantApp();
3093     }
3094 
isInstalled(@ullable PackageInfo pi)3095     private static boolean isInstalled(@Nullable PackageInfo pi) {
3096         return (pi != null) && isInstalled(pi.applicationInfo);
3097     }
3098 
isInstalled(@ullable ActivityInfo ai)3099     private static boolean isInstalled(@Nullable ActivityInfo ai) {
3100         return (ai != null) && isInstalled(ai.applicationInfo);
3101     }
3102 
isInstalledOrNull(ApplicationInfo ai)3103     private static ApplicationInfo isInstalledOrNull(ApplicationInfo ai) {
3104         return isInstalled(ai) ? ai : null;
3105     }
3106 
isInstalledOrNull(PackageInfo pi)3107     private static PackageInfo isInstalledOrNull(PackageInfo pi) {
3108         return isInstalled(pi) ? pi : null;
3109     }
3110 
isInstalledOrNull(ActivityInfo ai)3111     private static ActivityInfo isInstalledOrNull(ActivityInfo ai) {
3112         return isInstalled(ai) ? ai : null;
3113     }
3114 
isPackageInstalled(String packageName, int userId)3115     boolean isPackageInstalled(String packageName, int userId) {
3116         return getApplicationInfo(packageName, userId) != null;
3117     }
3118 
isEphemeralApp(String packageName, int userId)3119     boolean isEphemeralApp(String packageName, int userId) {
3120         return isEphemeralApp(getApplicationInfo(packageName, userId));
3121     }
3122 
3123     @Nullable
injectXmlMetaData(ActivityInfo activityInfo, String key)3124     XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
3125         return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
3126     }
3127 
3128     @Nullable
injectGetResourcesForApplicationAsUser(String packageName, int userId)3129     Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) {
3130         final long start = injectElapsedRealtime();
3131         final long token = injectClearCallingIdentity();
3132         try {
3133             return mContext.getPackageManager().getResourcesForApplicationAsUser(
3134                     packageName, userId);
3135         } catch (NameNotFoundException e) {
3136             Slog.e(TAG, "Resources for package " + packageName + " not found");
3137             return null;
3138         } finally {
3139             injectRestoreCallingIdentity(token);
3140 
3141             logDurationStat(Stats.GET_APPLICATION_RESOURCES, start);
3142         }
3143     }
3144 
getMainActivityIntent()3145     private Intent getMainActivityIntent() {
3146         final Intent intent = new Intent(Intent.ACTION_MAIN);
3147         intent.addCategory(LAUNCHER_INTENT_CATEGORY);
3148         return intent;
3149     }
3150 
3151     /**
3152      * Same as queryIntentActivitiesAsUser, except it makes sure the package is installed,
3153      * and only returns exported activities.
3154      */
3155     @NonNull
3156     @VisibleForTesting
queryActivities(@onNull Intent baseIntent, @NonNull String packageName, @Nullable ComponentName activity, int userId)3157     List<ResolveInfo> queryActivities(@NonNull Intent baseIntent,
3158             @NonNull String packageName, @Nullable ComponentName activity, int userId) {
3159 
3160         baseIntent.setPackage(Preconditions.checkNotNull(packageName));
3161         if (activity != null) {
3162             baseIntent.setComponent(activity);
3163         }
3164         return queryActivities(baseIntent, userId, /* exportedOnly =*/ true);
3165     }
3166 
3167     @NonNull
queryActivities(@onNull Intent intent, int userId, boolean exportedOnly)3168     List<ResolveInfo> queryActivities(@NonNull Intent intent, int userId,
3169             boolean exportedOnly) {
3170         final List<ResolveInfo> resolved;
3171         final long token = injectClearCallingIdentity();
3172         try {
3173             resolved =
3174                     mContext.getPackageManager().queryIntentActivitiesAsUser(
3175                             intent, PACKAGE_MATCH_FLAGS, userId);
3176         } finally {
3177             injectRestoreCallingIdentity(token);
3178         }
3179         if (resolved == null || resolved.size() == 0) {
3180             return EMPTY_RESOLVE_INFO;
3181         }
3182         // Make sure the package is installed.
3183         if (!isInstalled(resolved.get(0).activityInfo)) {
3184             return EMPTY_RESOLVE_INFO;
3185         }
3186         if (exportedOnly) {
3187             resolved.removeIf(ACTIVITY_NOT_EXPORTED);
3188         }
3189         return resolved;
3190     }
3191 
3192     /**
3193      * Return the main activity that is enabled and exported.  If multiple activities are found,
3194      * return the first one.
3195      */
3196     @Nullable
injectGetDefaultMainActivity(@onNull String packageName, int userId)3197     ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) {
3198         final long start = injectElapsedRealtime();
3199         try {
3200             final List<ResolveInfo> resolved =
3201                     queryActivities(getMainActivityIntent(), packageName, null, userId);
3202             return resolved.size() == 0 ? null : resolved.get(0).activityInfo.getComponentName();
3203         } finally {
3204             logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start);
3205         }
3206     }
3207 
3208     /**
3209      * Return whether an activity is enabled, exported and main.
3210      */
injectIsMainActivity(@onNull ComponentName activity, int userId)3211     boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
3212         final long start = injectElapsedRealtime();
3213         try {
3214             if (activity == null) {
3215                 wtf("null activity detected");
3216                 return false;
3217             }
3218             if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) {
3219                 return true;
3220             }
3221             final List<ResolveInfo> resolved = queryActivities(
3222                     getMainActivityIntent(), activity.getPackageName(), activity, userId);
3223             return resolved.size() > 0;
3224         } finally {
3225             logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
3226         }
3227     }
3228 
3229     /**
3230      * Create a dummy "main activity" component name which is used to create a dynamic shortcut
3231      * with no main activity temporarily.
3232      */
3233     @NonNull
getDummyMainActivity(@onNull String packageName)3234     ComponentName getDummyMainActivity(@NonNull String packageName) {
3235         return new ComponentName(packageName, DUMMY_MAIN_ACTIVITY);
3236     }
3237 
isDummyMainActivity(@ullable ComponentName name)3238     boolean isDummyMainActivity(@Nullable ComponentName name) {
3239         return name != null && DUMMY_MAIN_ACTIVITY.equals(name.getClassName());
3240     }
3241 
3242     /**
3243      * Return all the enabled, exported and main activities from a package.
3244      */
3245     @NonNull
injectGetMainActivities(@onNull String packageName, int userId)3246     List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) {
3247         final long start = injectElapsedRealtime();
3248         try {
3249             return queryActivities(getMainActivityIntent(), packageName, null, userId);
3250         } finally {
3251             logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
3252         }
3253     }
3254 
3255     /**
3256      * Return whether an activity is enabled and exported.
3257      */
3258     @VisibleForTesting
injectIsActivityEnabledAndExported( @onNull ComponentName activity, @UserIdInt int userId)3259     boolean injectIsActivityEnabledAndExported(
3260             @NonNull ComponentName activity, @UserIdInt int userId) {
3261         final long start = injectElapsedRealtime();
3262         try {
3263             return queryActivities(new Intent(), activity.getPackageName(), activity, userId)
3264                     .size() > 0;
3265         } finally {
3266             logDurationStat(Stats.IS_ACTIVITY_ENABLED, start);
3267         }
3268     }
3269 
3270     /**
3271      * Get the {@link LauncherApps#ACTION_CONFIRM_PIN_SHORTCUT} or
3272      * {@link LauncherApps#ACTION_CONFIRM_PIN_APPWIDGET} activity in a given package depending on
3273      * the requestType.
3274      */
3275     @Nullable
injectGetPinConfirmationActivity(@onNull String launcherPackageName, int launcherUserId, int requestType)3276     ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
3277             int launcherUserId, int requestType) {
3278         Preconditions.checkNotNull(launcherPackageName);
3279         String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
3280                 LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
3281                 LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
3282 
3283         final Intent confirmIntent = new Intent(action).setPackage(launcherPackageName);
3284         final List<ResolveInfo> candidates = queryActivities(
3285                 confirmIntent, launcherUserId, /* exportedOnly =*/ false);
3286         for (ResolveInfo ri : candidates) {
3287             return ri.activityInfo.getComponentName();
3288         }
3289         return null;
3290     }
3291 
injectIsSafeModeEnabled()3292     boolean injectIsSafeModeEnabled() {
3293         final long token = injectClearCallingIdentity();
3294         try {
3295             return IWindowManager.Stub
3296                     .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE))
3297                     .isSafeModeEnabled();
3298         } catch (RemoteException e) {
3299             return false; // Shouldn't happen though.
3300         } finally {
3301             injectRestoreCallingIdentity(token);
3302         }
3303     }
3304 
3305     /**
3306      * If {@code userId} is of a managed profile, return the parent user ID.  Otherwise return
3307      * itself.
3308      */
getParentOrSelfUserId(int userId)3309     int getParentOrSelfUserId(int userId) {
3310         final long token = injectClearCallingIdentity();
3311         try {
3312             final UserInfo parent = mUserManager.getProfileParent(userId);
3313             return (parent != null) ? parent.id : userId;
3314         } finally {
3315             injectRestoreCallingIdentity(token);
3316         }
3317     }
3318 
injectSendIntentSender(IntentSender intentSender, Intent extras)3319     void injectSendIntentSender(IntentSender intentSender, Intent extras) {
3320         if (intentSender == null) {
3321             return;
3322         }
3323         try {
3324             intentSender.sendIntent(mContext, /* code= */ 0, extras,
3325                     /* onFinished=*/ null, /* handler= */ null);
3326         } catch (SendIntentException e) {
3327             Slog.w(TAG, "sendIntent failed().", e);
3328         }
3329     }
3330 
3331     // === Backup & restore ===
3332 
shouldBackupApp(String packageName, int userId)3333     boolean shouldBackupApp(String packageName, int userId) {
3334         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
3335     }
3336 
shouldBackupApp(PackageInfo pi)3337     boolean shouldBackupApp(PackageInfo pi) {
3338         return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
3339     }
3340 
3341     @Override
getBackupPayload(@serIdInt int userId)3342     public byte[] getBackupPayload(@UserIdInt int userId) {
3343         enforceSystem();
3344         if (DEBUG) {
3345             Slog.d(TAG, "Backing up user " + userId);
3346         }
3347         synchronized (mLock) {
3348             if (!isUserUnlockedL(userId)) {
3349                 wtf("Can't backup: user " + userId + " is locked or not running");
3350                 return null;
3351             }
3352 
3353             final ShortcutUser user = getUserShortcutsLocked(userId);
3354             if (user == null) {
3355                 wtf("Can't backup: user not found: id=" + userId);
3356                 return null;
3357             }
3358 
3359             // Update the signatures for all packages.
3360             user.forAllPackageItems(spi -> spi.refreshPackageSignatureAndSave());
3361 
3362             // Set the version code for the launchers.
3363             // We shouldn't do this for publisher packages, because we don't want to update the
3364             // version code without rescanning the manifest.
3365             user.forAllLaunchers(launcher -> launcher.ensureVersionInfo());
3366 
3367             // Save to the filesystem.
3368             scheduleSaveUser(userId);
3369             saveDirtyInfo();
3370 
3371             // Note, in case of backup, we don't have to wait on bitmap saving, because we don't
3372             // back up bitmaps anyway.
3373 
3374             // Then create the backup payload.
3375             final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
3376             try {
3377                 saveUserInternalLocked(userId, os, /* forBackup */ true);
3378             } catch (XmlPullParserException | IOException e) {
3379                 // Shouldn't happen.
3380                 Slog.w(TAG, "Backup failed.", e);
3381                 return null;
3382             }
3383             return os.toByteArray();
3384         }
3385     }
3386 
3387     @Override
applyRestore(byte[] payload, @UserIdInt int userId)3388     public void applyRestore(byte[] payload, @UserIdInt int userId) {
3389         enforceSystem();
3390         if (DEBUG) {
3391             Slog.d(TAG, "Restoring user " + userId);
3392         }
3393         synchronized (mLock) {
3394             if (!isUserUnlockedL(userId)) {
3395                 wtf("Can't restore: user " + userId + " is locked or not running");
3396                 return;
3397             }
3398             // Actually do restore.
3399             final ShortcutUser restored;
3400             final ByteArrayInputStream is = new ByteArrayInputStream(payload);
3401             try {
3402                 restored = loadUserInternal(userId, is, /* fromBackup */ true);
3403             } catch (XmlPullParserException | IOException | InvalidFileFormatException e) {
3404                 Slog.w(TAG, "Restoration failed.", e);
3405                 return;
3406             }
3407             getUserShortcutsLocked(userId).mergeRestoredFile(restored);
3408 
3409             // Rescan all packages to re-publish manifest shortcuts and do other checks.
3410             rescanUpdatedPackagesLocked(userId,
3411                     0 // lastScanTime = 0; rescan all packages.
3412                     );
3413 
3414             saveUserLocked(userId);
3415         }
3416     }
3417 
3418     // === Dump ===
3419 
3420     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)3421     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3422         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
3423         dumpNoCheck(fd, pw, args);
3424     }
3425 
3426     @VisibleForTesting
dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args)3427     void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) {
3428         boolean checkin = false;
3429         boolean clear = false;
3430         if (args != null) {
3431             for (String arg : args) {
3432                 if ("-c".equals(arg)) {
3433                     checkin = true;
3434                 } else if ("--checkin".equals(arg)) {
3435                     checkin = true;
3436                     clear = true;
3437                 }
3438             }
3439         }
3440 
3441         if (checkin) {
3442             dumpCheckin(pw, clear);
3443         } else {
3444             dumpInner(pw);
3445         }
3446     }
3447 
dumpInner(PrintWriter pw)3448     private void dumpInner(PrintWriter pw) {
3449         synchronized (mLock) {
3450             final long now = injectCurrentTimeMillis();
3451             pw.print("Now: [");
3452             pw.print(now);
3453             pw.print("] ");
3454             pw.print(formatTime(now));
3455 
3456             pw.print("  Raw last reset: [");
3457             pw.print(mRawLastResetTime);
3458             pw.print("] ");
3459             pw.print(formatTime(mRawLastResetTime));
3460 
3461             final long last = getLastResetTimeLocked();
3462             pw.print("  Last reset: [");
3463             pw.print(last);
3464             pw.print("] ");
3465             pw.print(formatTime(last));
3466 
3467             final long next = getNextResetTimeLocked();
3468             pw.print("  Next reset: [");
3469             pw.print(next);
3470             pw.print("] ");
3471             pw.print(formatTime(next));
3472 
3473             pw.print("  Config:");
3474             pw.print("    Max icon dim: ");
3475             pw.println(mMaxIconDimension);
3476             pw.print("    Icon format: ");
3477             pw.println(mIconPersistFormat);
3478             pw.print("    Icon quality: ");
3479             pw.println(mIconPersistQuality);
3480             pw.print("    saveDelayMillis: ");
3481             pw.println(mSaveDelayMillis);
3482             pw.print("    resetInterval: ");
3483             pw.println(mResetInterval);
3484             pw.print("    maxUpdatesPerInterval: ");
3485             pw.println(mMaxUpdatesPerInterval);
3486             pw.print("    maxShortcutsPerActivity: ");
3487             pw.println(mMaxShortcuts);
3488             pw.println();
3489 
3490             pw.println("  Stats:");
3491             synchronized (mStatLock) {
3492                 for (int i = 0; i < Stats.COUNT; i++) {
3493                     dumpStatLS(pw, "    ", i);
3494                 }
3495             }
3496 
3497             pw.println();
3498             pw.print("  #Failures: ");
3499             pw.println(mWtfCount);
3500 
3501             if (mLastWtfStacktrace != null) {
3502                 pw.print("  Last failure stack trace: ");
3503                 pw.println(Log.getStackTraceString(mLastWtfStacktrace));
3504             }
3505 
3506             pw.println();
3507             mShortcutBitmapSaver.dumpLocked(pw, "  ");
3508 
3509             for (int i = 0; i < mUsers.size(); i++) {
3510                 pw.println();
3511                 mUsers.valueAt(i).dump(pw, "  ");
3512             }
3513 
3514             pw.println();
3515             pw.println("  UID state:");
3516 
3517             for (int i = 0; i < mUidState.size(); i++) {
3518                 final int uid = mUidState.keyAt(i);
3519                 final int state = mUidState.valueAt(i);
3520                 pw.print("    UID=");
3521                 pw.print(uid);
3522                 pw.print(" state=");
3523                 pw.print(state);
3524                 if (isProcessStateForeground(state)) {
3525                     pw.print("  [FG]");
3526                 }
3527                 pw.print("  last FG=");
3528                 pw.print(mUidLastForegroundElapsedTime.get(uid));
3529                 pw.println();
3530             }
3531         }
3532     }
3533 
formatTime(long time)3534     static String formatTime(long time) {
3535         Time tobj = new Time();
3536         tobj.set(time);
3537         return tobj.format("%Y-%m-%d %H:%M:%S");
3538     }
3539 
dumpStatLS(PrintWriter pw, String prefix, int statId)3540     private void dumpStatLS(PrintWriter pw, String prefix, int statId) {
3541         pw.print(prefix);
3542         final int count = mCountStats[statId];
3543         final long dur = mDurationStats[statId];
3544         pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
3545                 STAT_LABELS[statId], count, dur,
3546                 (count == 0 ? 0 : ((double) dur) / count)));
3547     }
3548 
3549     /**
3550      * Dumpsys for checkin.
3551      *
3552      * @param clear if true, clear the history information.  Some other system services have this
3553      * behavior but shortcut service doesn't for now.
3554      */
dumpCheckin(PrintWriter pw, boolean clear)3555     private  void dumpCheckin(PrintWriter pw, boolean clear) {
3556         synchronized (mLock) {
3557             try {
3558                 final JSONArray users = new JSONArray();
3559 
3560                 for (int i = 0; i < mUsers.size(); i++) {
3561                     users.put(mUsers.valueAt(i).dumpCheckin(clear));
3562                 }
3563 
3564                 final JSONObject result = new JSONObject();
3565 
3566                 result.put(KEY_SHORTCUT, users);
3567                 result.put(KEY_LOW_RAM, injectIsLowRamDevice());
3568                 result.put(KEY_ICON_SIZE, mMaxIconDimension);
3569 
3570                 pw.println(result.toString(1));
3571             } catch (JSONException e) {
3572                 Slog.e(TAG, "Unable to write in json", e);
3573             }
3574         }
3575     }
3576 
3577     // === Shell support ===
3578 
3579     @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)3580     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
3581             String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
3582 
3583         enforceShell();
3584 
3585         final long token = injectClearCallingIdentity();
3586         try {
3587             final int status = (new MyShellCommand()).exec(this, in, out, err, args, callback,
3588                     resultReceiver);
3589             resultReceiver.send(status, null);
3590         } finally {
3591             injectRestoreCallingIdentity(token);
3592         }
3593     }
3594 
3595     static class CommandException extends Exception {
CommandException(String message)3596         public CommandException(String message) {
3597             super(message);
3598         }
3599     }
3600 
3601     /**
3602      * Handle "adb shell cmd".
3603      */
3604     private class MyShellCommand extends ShellCommand {
3605 
3606         private int mUserId = UserHandle.USER_SYSTEM;
3607 
parseOptionsLocked(boolean takeUser)3608         private void parseOptionsLocked(boolean takeUser)
3609                 throws CommandException {
3610             String opt;
3611             while ((opt = getNextOption()) != null) {
3612                 switch (opt) {
3613                     case "--user":
3614                         if (takeUser) {
3615                             mUserId = UserHandle.parseUserArg(getNextArgRequired());
3616                             if (!isUserUnlockedL(mUserId)) {
3617                                 throw new CommandException(
3618                                         "User " + mUserId + " is not running or locked");
3619                             }
3620                             break;
3621                         }
3622                         // fallthrough
3623                     default:
3624                         throw new CommandException("Unknown option: " + opt);
3625                 }
3626             }
3627         }
3628 
3629         @Override
onCommand(String cmd)3630         public int onCommand(String cmd) {
3631             if (cmd == null) {
3632                 return handleDefaultCommands(cmd);
3633             }
3634             final PrintWriter pw = getOutPrintWriter();
3635             try {
3636                 switch (cmd) {
3637                     case "reset-throttling":
3638                         handleResetThrottling();
3639                         break;
3640                     case "reset-all-throttling":
3641                         handleResetAllThrottling();
3642                         break;
3643                     case "override-config":
3644                         handleOverrideConfig();
3645                         break;
3646                     case "reset-config":
3647                         handleResetConfig();
3648                         break;
3649                     case "clear-default-launcher":
3650                         handleClearDefaultLauncher();
3651                         break;
3652                     case "get-default-launcher":
3653                         handleGetDefaultLauncher();
3654                         break;
3655                     case "unload-user":
3656                         handleUnloadUser();
3657                         break;
3658                     case "clear-shortcuts":
3659                         handleClearShortcuts();
3660                         break;
3661                     case "verify-states": // hidden command to verify various internal states.
3662                         handleVerifyStates();
3663                         break;
3664                     default:
3665                         return handleDefaultCommands(cmd);
3666                 }
3667             } catch (CommandException e) {
3668                 pw.println("Error: " + e.getMessage());
3669                 return 1;
3670             }
3671             pw.println("Success");
3672             return 0;
3673         }
3674 
3675         @Override
onHelp()3676         public void onHelp() {
3677             final PrintWriter pw = getOutPrintWriter();
3678             pw.println("Usage: cmd shortcut COMMAND [options ...]");
3679             pw.println();
3680             pw.println("cmd shortcut reset-throttling [--user USER_ID]");
3681             pw.println("    Reset throttling for all packages and users");
3682             pw.println();
3683             pw.println("cmd shortcut reset-all-throttling");
3684             pw.println("    Reset the throttling state for all users");
3685             pw.println();
3686             pw.println("cmd shortcut override-config CONFIG");
3687             pw.println("    Override the configuration for testing (will last until reboot)");
3688             pw.println();
3689             pw.println("cmd shortcut reset-config");
3690             pw.println("    Reset the configuration set with \"update-config\"");
3691             pw.println();
3692             pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
3693             pw.println("    Clear the cached default launcher");
3694             pw.println();
3695             pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
3696             pw.println("    Show the default launcher");
3697             pw.println();
3698             pw.println("cmd shortcut unload-user [--user USER_ID]");
3699             pw.println("    Unload a user from the memory");
3700             pw.println("    (This should not affect any observable behavior)");
3701             pw.println();
3702             pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE");
3703             pw.println("    Remove all shortcuts from a package, including pinned shortcuts");
3704             pw.println();
3705         }
3706 
handleResetThrottling()3707         private void handleResetThrottling() throws CommandException {
3708             synchronized (mLock) {
3709                 parseOptionsLocked(/* takeUser =*/ true);
3710 
3711                 Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId);
3712 
3713                 resetThrottlingInner(mUserId);
3714             }
3715         }
3716 
handleResetAllThrottling()3717         private void handleResetAllThrottling() {
3718             Slog.i(TAG, "cmd: handleResetAllThrottling");
3719 
3720             resetAllThrottlingInner();
3721         }
3722 
handleOverrideConfig()3723         private void handleOverrideConfig() throws CommandException {
3724             final String config = getNextArgRequired();
3725 
3726             Slog.i(TAG, "cmd: handleOverrideConfig: " + config);
3727 
3728             synchronized (mLock) {
3729                 if (!updateConfigurationLocked(config)) {
3730                     throw new CommandException("override-config failed.  See logcat for details.");
3731                 }
3732             }
3733         }
3734 
handleResetConfig()3735         private void handleResetConfig() {
3736             Slog.i(TAG, "cmd: handleResetConfig");
3737 
3738             synchronized (mLock) {
3739                 loadConfigurationLocked();
3740             }
3741         }
3742 
clearLauncher()3743         private void clearLauncher() {
3744             synchronized (mLock) {
3745                 getUserShortcutsLocked(mUserId).forceClearLauncher();
3746             }
3747         }
3748 
showLauncher()3749         private void showLauncher() {
3750             synchronized (mLock) {
3751                 // This ensures to set the cached launcher.  Package name doesn't matter.
3752                 hasShortcutHostPermissionInner("-", mUserId);
3753 
3754                 getOutPrintWriter().println("Launcher: "
3755                         + getUserShortcutsLocked(mUserId).getLastKnownLauncher());
3756             }
3757         }
3758 
handleClearDefaultLauncher()3759         private void handleClearDefaultLauncher() throws CommandException {
3760             synchronized (mLock) {
3761                 parseOptionsLocked(/* takeUser =*/ true);
3762 
3763                 clearLauncher();
3764             }
3765         }
3766 
handleGetDefaultLauncher()3767         private void handleGetDefaultLauncher() throws CommandException {
3768             synchronized (mLock) {
3769                 parseOptionsLocked(/* takeUser =*/ true);
3770 
3771                 clearLauncher();
3772                 showLauncher();
3773             }
3774         }
3775 
handleUnloadUser()3776         private void handleUnloadUser() throws CommandException {
3777             synchronized (mLock) {
3778                 parseOptionsLocked(/* takeUser =*/ true);
3779 
3780                 Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId);
3781 
3782                 ShortcutService.this.handleStopUser(mUserId);
3783             }
3784         }
3785 
handleClearShortcuts()3786         private void handleClearShortcuts() throws CommandException {
3787             synchronized (mLock) {
3788                 parseOptionsLocked(/* takeUser =*/ true);
3789                 final String packageName = getNextArgRequired();
3790 
3791                 Slog.i(TAG, "cmd: handleClearShortcuts: user" + mUserId + ", " + packageName);
3792 
3793                 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId,
3794                         /* appStillExists = */ true);
3795             }
3796         }
3797 
handleVerifyStates()3798         private void handleVerifyStates() throws CommandException {
3799             try {
3800                 verifyStatesForce(); // This will throw when there's an issue.
3801             } catch (Throwable th) {
3802                 throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th));
3803             }
3804         }
3805     }
3806 
3807     // === Unit test support ===
3808 
3809     // Injection point.
3810     @VisibleForTesting
injectCurrentTimeMillis()3811     long injectCurrentTimeMillis() {
3812         return System.currentTimeMillis();
3813     }
3814 
3815     @VisibleForTesting
injectElapsedRealtime()3816     long injectElapsedRealtime() {
3817         return SystemClock.elapsedRealtime();
3818     }
3819 
3820     @VisibleForTesting
injectUptimeMillis()3821     long injectUptimeMillis() {
3822         return SystemClock.uptimeMillis();
3823     }
3824 
3825     // Injection point.
3826     @VisibleForTesting
injectBinderCallingUid()3827     int injectBinderCallingUid() {
3828         return getCallingUid();
3829     }
3830 
getCallingUserId()3831     private int getCallingUserId() {
3832         return UserHandle.getUserId(injectBinderCallingUid());
3833     }
3834 
3835     // Injection point.
3836     @VisibleForTesting
injectClearCallingIdentity()3837     long injectClearCallingIdentity() {
3838         return Binder.clearCallingIdentity();
3839     }
3840 
3841     // Injection point.
3842     @VisibleForTesting
injectRestoreCallingIdentity(long token)3843     void injectRestoreCallingIdentity(long token) {
3844         Binder.restoreCallingIdentity(token);
3845     }
3846 
3847     // Injection point.
3848     @VisibleForTesting
injectBuildFingerprint()3849     String injectBuildFingerprint() {
3850         return Build.FINGERPRINT;
3851     }
3852 
wtf(String message)3853     final void wtf(String message) {
3854         wtf(message, /* exception= */ null);
3855     }
3856 
3857     // Injection point.
wtf(String message, Throwable e)3858     void wtf(String message, Throwable e) {
3859         if (e == null) {
3860             e = new RuntimeException("Stacktrace");
3861         }
3862         synchronized (mLock) {
3863             mWtfCount++;
3864             mLastWtfStacktrace = new Exception("Last failure was logged here:");
3865         }
3866         Slog.wtf(TAG, message, e);
3867     }
3868 
3869     @VisibleForTesting
injectSystemDataPath()3870     File injectSystemDataPath() {
3871         return Environment.getDataSystemDirectory();
3872     }
3873 
3874     @VisibleForTesting
injectUserDataPath(@serIdInt int userId)3875     File injectUserDataPath(@UserIdInt int userId) {
3876         return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
3877     }
3878 
3879     @VisibleForTesting
injectIsLowRamDevice()3880     boolean injectIsLowRamDevice() {
3881         return ActivityManager.isLowRamDeviceStatic();
3882     }
3883 
3884     @VisibleForTesting
injectRegisterUidObserver(IUidObserver observer, int which)3885     void injectRegisterUidObserver(IUidObserver observer, int which) {
3886         try {
3887             ActivityManager.getService().registerUidObserver(observer, which,
3888                     ActivityManager.PROCESS_STATE_UNKNOWN, null);
3889         } catch (RemoteException shouldntHappen) {
3890         }
3891     }
3892 
getUserBitmapFilePath(@serIdInt int userId)3893     File getUserBitmapFilePath(@UserIdInt int userId) {
3894         return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
3895     }
3896 
3897     @VisibleForTesting
getShortcutsForTest()3898     SparseArray<ShortcutUser> getShortcutsForTest() {
3899         return mUsers;
3900     }
3901 
3902     @VisibleForTesting
getMaxShortcutsForTest()3903     int getMaxShortcutsForTest() {
3904         return mMaxShortcuts;
3905     }
3906 
3907     @VisibleForTesting
getMaxUpdatesPerIntervalForTest()3908     int getMaxUpdatesPerIntervalForTest() {
3909         return mMaxUpdatesPerInterval;
3910     }
3911 
3912     @VisibleForTesting
getResetIntervalForTest()3913     long getResetIntervalForTest() {
3914         return mResetInterval;
3915     }
3916 
3917     @VisibleForTesting
getMaxIconDimensionForTest()3918     int getMaxIconDimensionForTest() {
3919         return mMaxIconDimension;
3920     }
3921 
3922     @VisibleForTesting
getIconPersistFormatForTest()3923     CompressFormat getIconPersistFormatForTest() {
3924         return mIconPersistFormat;
3925     }
3926 
3927     @VisibleForTesting
getIconPersistQualityForTest()3928     int getIconPersistQualityForTest() {
3929         return mIconPersistQuality;
3930     }
3931 
3932     @VisibleForTesting
getPackageShortcutForTest(String packageName, int userId)3933     ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
3934         synchronized (mLock) {
3935             final ShortcutUser user = mUsers.get(userId);
3936             if (user == null) return null;
3937 
3938             return user.getAllPackagesForTest().get(packageName);
3939         }
3940     }
3941 
3942     @VisibleForTesting
getPackageShortcutForTest(String packageName, String shortcutId, int userId)3943     ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
3944         synchronized (mLock) {
3945             final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
3946             if (pkg == null) return null;
3947 
3948             return pkg.findShortcutById(shortcutId);
3949         }
3950     }
3951 
3952     @VisibleForTesting
getLauncherShortcutForTest(String packageName, int userId)3953     ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) {
3954         synchronized (mLock) {
3955             final ShortcutUser user = mUsers.get(userId);
3956             if (user == null) return null;
3957 
3958             return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName));
3959         }
3960     }
3961 
3962     @VisibleForTesting
getShortcutRequestPinProcessorForTest()3963     ShortcutRequestPinProcessor getShortcutRequestPinProcessorForTest() {
3964         return mShortcutRequestPinProcessor;
3965     }
3966 
3967     /**
3968      * Control whether {@link #verifyStates} should be performed.  We always perform it during unit
3969      * tests.
3970      */
3971     @VisibleForTesting
injectShouldPerformVerification()3972     boolean injectShouldPerformVerification() {
3973         return DEBUG;
3974     }
3975 
3976     /**
3977      * Check various internal states and throws if there's any inconsistency.
3978      * This is normally only enabled during unit tests.
3979      */
verifyStates()3980     final void verifyStates() {
3981         if (injectShouldPerformVerification()) {
3982             verifyStatesInner();
3983         }
3984     }
3985 
verifyStatesForce()3986     private final void verifyStatesForce() {
3987         verifyStatesInner();
3988     }
3989 
verifyStatesInner()3990     private void verifyStatesInner() {
3991         synchronized (mLock) {
3992             forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
3993         }
3994     }
3995 
3996     @VisibleForTesting
waitForBitmapSavesForTest()3997     void waitForBitmapSavesForTest() {
3998         synchronized (mLock) {
3999             mShortcutBitmapSaver.waitForAllSavesLocked();
4000         }
4001     }
4002 }
4003