2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.server.pm;
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.UserIdInt;
21 import android.app.ActivityManager;
22 import android.app.ActivityManagerNative;
23 import android.app.AppGlobals;
24 import android.app.IUidObserver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.IPackageManager;
30 import android.content.pm.IShortcutService;
31 import android.content.pm.LauncherApps;
32 import android.content.pm.LauncherApps.ShortcutQuery;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManagerInternal;
36 import android.content.pm.ParceledListSlice;
37 import android.content.pm.ResolveInfo;
38 import android.content.pm.ShortcutInfo;
39 import android.content.pm.ShortcutServiceInternal;
40 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
41 import android.graphics.Bitmap;
42 import android.graphics.Bitmap.CompressFormat;
43 import android.graphics.Canvas;
44 import android.graphics.RectF;
45 import android.graphics.drawable.Icon;
46 import android.os.Binder;
47 import android.os.Environment;
48 import android.os.FileUtils;
49 import android.os.Handler;
50 import android.os.Looper;
51 import android.os.ParcelFileDescriptor;
52 import android.os.PersistableBundle;
53 import android.os.Process;
54 import android.os.RemoteException;
55 import android.os.ResultReceiver;
56 import android.os.SELinux;
57 import android.os.ShellCommand;
58 import android.os.SystemClock;
59 import android.os.UserHandle;
60 import android.os.UserManager;
61 import android.text.TextUtils;
62 import android.text.format.Time;
63 import android.util.ArraySet;
64 import android.util.AtomicFile;
65 import android.util.KeyValueListParser;
66 import android.util.Slog;
67 import android.util.SparseArray;
68 import android.util.SparseIntArray;
69 import android.util.SparseLongArray;
70 import android.util.TypedValue;
71 import android.util.Xml;
73 import com.android.internal.annotations.GuardedBy;
74 import com.android.internal.annotations.VisibleForTesting;
75 import com.android.internal.content.PackageMonitor;
76 import com.android.internal.os.BackgroundThread;
77 import com.android.internal.util.ArrayUtils;
78 import com.android.internal.util.FastXmlSerializer;
79 import com.android.internal.util.Preconditions;
80 import com.android.server.LocalServices;
81 import com.android.server.SystemService;
82 import com.android.server.pm.ShortcutUser.PackageWithUser;
84 import libcore.io.IoUtils;
86 import org.xmlpull.v1.XmlPullParser;
87 import org.xmlpull.v1.XmlPullParserException;
88 import org.xmlpull.v1.XmlSerializer;
90 import java.io.BufferedInputStream;
91 import java.io.BufferedOutputStream;
92 import java.io.ByteArrayInputStream;
93 import java.io.ByteArrayOutputStream;
95 import java.io.FileDescriptor;
96 import java.io.FileInputStream;
97 import java.io.FileNotFoundException;
98 import java.io.FileOutputStream;
99 import java.io.IOException;
100 import java.io.InputStream;
101 import java.io.OutputStream;
102 import java.io.PrintWriter;
103 import java.net.URISyntaxException;
104 import java.nio.charset.StandardCharsets;
105 import java.util.ArrayList;
106 import java.util.List;
107 import java.util.concurrent.atomic.AtomicLong;
108 import java.util.function.Consumer;
109 import java.util.function.Predicate;
114 * - Default launcher check does take a few ms. Worth caching.
116 * - Clear data -> remove all dynamic? but not the pinned?
118 * - Scan and remove orphan bitmaps (just in case).
120 * - Detect when already registered instances are passed to APIs again, which might break
121 * internal bitmap handling.
123 * - Add more call stats.
125 public class ShortcutService extends IShortcutService.Stub {
126 static final String TAG = "ShortcutService";
128 public static final boolean FEATURE_ENABLED = false;
130 static final boolean DEBUG = false; // STOPSHIP if true
131 static final boolean DEBUG_LOAD = false; // STOPSHIP if true
132 static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
135 static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
138 static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
141 static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
144 static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
147 static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
150 static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
153 static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
156 static final int DEFAULT_SAVE_DELAY_MS = 3000;
159 static final String FILENAME_BASE_STATE = "shortcut_service.xml";
162 static final String DIRECTORY_PER_USER = "shortcut_service";
165 static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
167 static final String DIRECTORY_BITMAPS = "bitmaps";
169 private static final String TAG_ROOT = "root";
170 private static final String TAG_LAST_RESET_TIME = "last_reset_time";
171 private static final String TAG_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale_seq_no";
173 private static final String ATTR_VALUE = "value";
176 interface ConfigConstants {
178 * Key name for the save delay, in milliseconds. (int)
180 String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
183 * Key name for the throttling reset interval, in seconds. (long)
185 String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
188 * Key name for the max number of modifying API calls per app for every interval. (int)
190 String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval";
193 * Key name for the max icon dimensions in DP, for non-low-memory devices.
195 String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
198 * Key name for the max icon dimensions in DP, for low-memory devices.
200 String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
203 * Key name for the max dynamic shortcuts per app. (int)
205 String KEY_MAX_SHORTCUTS = "max_shortcuts";
208 * Key name for icon compression quality, 0-100.
210 String KEY_ICON_QUALITY = "icon_quality";
213 * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
215 String KEY_ICON_FORMAT = "icon_format";
218 final Context mContext;
220 private final Object mLock = new Object();
222 private final Handler mHandler;
225 private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
228 private long mRawLastResetTime;
231 * User ID -> UserShortcuts
234 private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
237 * Max number of dynamic shortcuts that each application can have at a time.
239 private int mMaxDynamicShortcuts;
242 * Max number of updating API calls that each application can make during the interval.
244 int mMaxUpdatesPerInterval;
247 * Actual throttling-reset interval. By default it's a day.
249 private long mResetInterval;
252 * Icon max width/height in pixels.
254 private int mMaxIconDimension;
256 private CompressFormat mIconPersistFormat;
257 private int mIconPersistQuality;
259 private int mSaveDelayMillis;
261 private final IPackageManager mIPackageManager;
262 private final PackageManagerInternal mPackageManagerInternal;
263 private final UserManager mUserManager;
266 final SparseIntArray mUidState = new SparseIntArray();
269 final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
272 private List<Integer> mDirtyUserIds = new ArrayList<>();
275 * A counter that increments every time the system locale changes. We keep track of it to reset
276 * throttling counters on the first call from each package after the last locale change.
278 * We need this mechanism because we can't do much in the locale change callback, which is
279 * {@link ShortcutServiceInternal#onSystemLocaleChangedNoLock()}.
281 private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong();
283 private static final int PACKAGE_MATCH_FLAGS =
284 PackageManager.MATCH_DIRECT_BOOT_AWARE
285 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
286 | PackageManager.MATCH_UNINSTALLED_PACKAGES;
291 int GET_DEFAULT_HOME = 0;
292 int GET_PACKAGE_INFO = 1;
293 int GET_PACKAGE_INFO_WITH_SIG = 2;
294 int GET_APPLICATION_INFO = 3;
295 int LAUNCHER_PERMISSION_CHECK = 4;
297 int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
300 final Object mStatLock = new Object();
302 @GuardedBy("mStatLock")
303 private final int[] mCountStats = new int[Stats.COUNT];
305 @GuardedBy("mStatLock")
306 private final long[] mDurationStats = new long[Stats.COUNT];
308 private static final int PROCESS_STATE_FOREGROUND_THRESHOLD =
309 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
311 public ShortcutService(Context context) {
312 this(context, BackgroundThread.get().getLooper());
316 ShortcutService(Context context, Looper looper) {
317 mContext = Preconditions.checkNotNull(context);
318 LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
319 mHandler = new Handler(looper);
320 mIPackageManager = AppGlobals.getPackageManager();
321 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
322 mUserManager = context.getSystemService(UserManager.class);
324 if (!FEATURE_ENABLED) {
327 mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
329 injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
330 | ActivityManager.UID_OBSERVER_GONE);
333 void logDurationStat(int statId, long start) {
334 synchronized (mStatLock) {
335 mCountStats[statId]++;
336 mDurationStats[statId] += (System.currentTimeMillis() - start);
340 public long getLocaleChangeSequenceNumber() {
341 return mLocaleChangeSequenceNumber.get();
344 final private IUidObserver mUidObserver = new IUidObserver.Stub() {
345 @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
346 handleOnUidStateChanged(uid, procState);
349 @Override public void onUidGone(int uid) throws RemoteException {
350 handleOnUidStateChanged(uid, ActivityManager.MAX_PROCESS_STATE);
353 @Override public void onUidActive(int uid) throws RemoteException {
356 @Override public void onUidIdle(int uid) throws RemoteException {
360 void handleOnUidStateChanged(int uid, int procState) {
361 if (DEBUG_PROCSTATE) {
362 Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
364 synchronized (mLock) {
365 mUidState.put(uid, procState);
367 // We need to keep track of last time an app comes to foreground.
368 // See ShortcutPackage.getApiCallCount() for how it's used.
369 // It doesn't have to be persisted, but it needs to be the elapsed time.
370 if (isProcessStateForeground(procState)) {
371 mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime());
376 private boolean isProcessStateForeground(int processState) {
377 return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
380 boolean isUidForegroundLocked(int uid) {
381 if (uid == Process.SYSTEM_UID) {
382 // IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
383 // so it's foreground anyway.
386 return isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE));
389 long getUidLastForegroundElapsedTimeLocked(int uid) {
390 return mUidLastForegroundElapsedTime.get(uid);
394 * System service lifecycle.
396 public static final class Lifecycle extends SystemService {
397 final ShortcutService mService;
399 public Lifecycle(Context context) {
401 mService = new ShortcutService(context);
405 public void onStart() {
406 publishBinderService(Context.SHORTCUT_SERVICE, mService);
410 public void onBootPhase(int phase) {
411 mService.onBootPhase(phase);
415 public void onCleanupUser(int userHandle) {
416 mService.handleCleanupUser(userHandle);
420 public void onUnlockUser(int userId) {
421 mService.handleUnlockUser(userId);
425 /** lifecycle event */
426 void onBootPhase(int phase) {
427 // We want to call initialize() to initialize the configurations, so we don't disable this.
429 Slog.d(TAG, "onBootPhase: " + phase);
432 case SystemService.PHASE_LOCK_SETTINGS_READY:
438 /** lifecycle event */
439 void handleUnlockUser(int userId) {
440 if (!FEATURE_ENABLED) {
443 synchronized (mLock) {
445 getUserShortcutsLocked(userId);
447 checkPackageChanges(userId);
451 /** lifecycle event */
452 void handleCleanupUser(int userId) {
453 if (!FEATURE_ENABLED) {
456 synchronized (mLock) {
457 unloadUserLocked(userId);
461 private void unloadUserLocked(int userId) {
463 Slog.d(TAG, "unloadUserLocked: user=" + userId);
465 // Save all dirty information.
469 mUsers.delete(userId);
472 /** Return the base state file name */
473 private AtomicFile getBaseStateFile() {
474 final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
476 return new AtomicFile(path);
480 * Init the instance. (load the state file, etc)
482 private void initialize() {
483 synchronized (mLock) {
484 loadConfigurationLocked();
485 loadBaseStateLocked();
490 * Load the configuration from Settings.
492 private void loadConfigurationLocked() {
493 updateConfigurationLocked(injectShortcutManagerConstants());
497 * Load the configuration from Settings.
500 boolean updateConfigurationLocked(String config) {
501 boolean result = true;
503 final KeyValueListParser parser = new KeyValueListParser(',');
505 parser.setString(config);
506 } catch (IllegalArgumentException e) {
507 // Failed to parse the settings string, log this and move on
509 Slog.e(TAG, "Bad shortcut manager settings", e);
513 mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
514 DEFAULT_SAVE_DELAY_MS));
516 mResetInterval = Math.max(1, parser.getLong(
517 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
520 mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
521 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
523 mMaxDynamicShortcuts = Math.max(0, (int) parser.getLong(
524 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP));
526 final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
527 ? (int) parser.getLong(
528 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
529 DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
530 : (int) parser.getLong(
531 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
532 DEFAULT_MAX_ICON_DIMENSION_DP));
534 mMaxIconDimension = injectDipToPixel(iconDimensionDp);
536 mIconPersistFormat = CompressFormat.valueOf(
537 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
539 mIconPersistQuality = (int) parser.getLong(
540 ConfigConstants.KEY_ICON_QUALITY,
541 DEFAULT_ICON_PERSIST_QUALITY);
547 String injectShortcutManagerConstants() {
548 return android.provider.Settings.Global.getString(
549 mContext.getContentResolver(),
550 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
554 int injectDipToPixel(int dip) {
555 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
556 mContext.getResources().getDisplayMetrics());
559 // === Persisting ===
562 static String parseStringAttribute(XmlPullParser parser, String attribute) {
563 return parser.getAttributeValue(null, attribute);
566 static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) {
567 return parseLongAttribute(parser, attribute) == 1;
570 static int parseIntAttribute(XmlPullParser parser, String attribute) {
571 return (int) parseLongAttribute(parser, attribute);
574 static int parseIntAttribute(XmlPullParser parser, String attribute, int def) {
575 return (int) parseLongAttribute(parser, attribute, def);
578 static long parseLongAttribute(XmlPullParser parser, String attribute) {
579 return parseLongAttribute(parser, attribute, 0);
582 static long parseLongAttribute(XmlPullParser parser, String attribute, long def) {
583 final String value = parseStringAttribute(parser, attribute);
584 if (TextUtils.isEmpty(value)) {
588 return Long.parseLong(value);
589 } catch (NumberFormatException e) {
590 Slog.e(TAG, "Error parsing long " + value);
596 static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
597 final String value = parseStringAttribute(parser, attribute);
598 if (TextUtils.isEmpty(value)) {
601 return ComponentName.unflattenFromString(value);
605 static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
606 final String value = parseStringAttribute(parser, attribute);
607 if (TextUtils.isEmpty(value)) {
611 return Intent.parseUri(value, /* flags =*/ 0);
612 } catch (URISyntaxException e) {
613 Slog.e(TAG, "Error parsing intent", e);
618 static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
619 if (TextUtils.isEmpty(value)) return;
621 out.startTag(null, tag);
622 out.attribute(null, ATTR_VALUE, value);
623 out.endTag(null, tag);
626 static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
627 writeTagValue(out, tag, Long.toString(value));
630 static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
631 if (name == null) return;
632 writeTagValue(out, tag, name.flattenToString());
635 static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
636 throws IOException, XmlPullParserException {
637 if (bundle == null) return;
639 out.startTag(null, tag);
640 bundle.saveToXml(out);
641 out.endTag(null, tag);
644 static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
645 if (TextUtils.isEmpty(value)) return;
647 out.attribute(null, name, value);
650 static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
651 writeAttr(out, name, String.valueOf(value));
654 static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
656 writeAttr(out, name, "1");
660 static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
661 if (comp == null) return;
662 writeAttr(out, name, comp.flattenToString());
665 static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
666 if (intent == null) return;
668 writeAttr(out, name, intent.toUri(/* flags =*/ 0));
672 void saveBaseStateLocked() {
673 final AtomicFile file = getBaseStateFile();
675 Slog.d(TAG, "Saving to " + file.getBaseFile());
678 FileOutputStream outs = null;
680 outs = file.startWrite();
683 XmlSerializer out = new FastXmlSerializer();
684 out.setOutput(outs, StandardCharsets.UTF_8.name());
685 out.startDocument(null, true);
686 out.startTag(null, TAG_ROOT);
689 writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
690 writeTagValue(out, TAG_LOCALE_CHANGE_SEQUENCE_NUMBER,
691 mLocaleChangeSequenceNumber.get());
694 out.endTag(null, TAG_ROOT);
698 file.finishWrite(outs);
699 } catch (IOException e) {
700 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
701 file.failWrite(outs);
705 private void loadBaseStateLocked() {
706 mRawLastResetTime = 0;
708 final AtomicFile file = getBaseStateFile();
710 Slog.d(TAG, "Loading from " + file.getBaseFile());
712 try (FileInputStream in = file.openRead()) {
713 XmlPullParser parser = Xml.newPullParser();
714 parser.setInput(in, StandardCharsets.UTF_8.name());
717 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
718 if (type != XmlPullParser.START_TAG) {
721 final int depth = parser.getDepth();
722 // Check the root tag
723 final String tag = parser.getName();
725 if (!TAG_ROOT.equals(tag)) {
726 Slog.e(TAG, "Invalid root tag: " + tag);
733 case TAG_LAST_RESET_TIME:
734 mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
736 case TAG_LOCALE_CHANGE_SEQUENCE_NUMBER:
737 mLocaleChangeSequenceNumber.set(parseLongAttribute(parser, ATTR_VALUE));
740 Slog.e(TAG, "Invalid tag: " + tag);
744 } catch (FileNotFoundException e) {
746 } catch (IOException|XmlPullParserException e) {
747 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
749 mRawLastResetTime = 0;
751 // Adjust the last reset time.
752 getLastResetTimeLocked();
755 private void saveUserLocked(@UserIdInt int userId) {
756 final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
758 Slog.d(TAG, "Saving to " + path);
761 final AtomicFile file = new AtomicFile(path);
762 FileOutputStream os = null;
764 os = file.startWrite();
766 saveUserInternalLocked(userId, os, /* forBackup= */ false);
768 file.finishWrite(os);
769 } catch (XmlPullParserException|IOException e) {
770 Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
775 private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
776 boolean forBackup) throws IOException, XmlPullParserException {
778 final BufferedOutputStream bos = new BufferedOutputStream(os);
781 XmlSerializer out = new FastXmlSerializer();
782 out.setOutput(bos, StandardCharsets.UTF_8.name());
783 out.startDocument(null, true);
785 getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
793 static IOException throwForInvalidTag(int depth, String tag) throws IOException {
794 throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
797 static void warnForInvalidTag(int depth, String tag) throws IOException {
798 Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
802 private ShortcutUser loadUserLocked(@UserIdInt int userId) {
803 final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
805 Slog.d(TAG, "Loading from " + path);
807 final AtomicFile file = new AtomicFile(path);
809 final FileInputStream in;
811 in = file.openRead();
812 } catch (FileNotFoundException e) {
814 Slog.d(TAG, "Not found " + path);
819 return loadUserInternal(userId, in, /* forBackup= */ false);
820 } catch (IOException|XmlPullParserException e) {
821 Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
824 IoUtils.closeQuietly(in);
828 private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
829 boolean fromBackup) throws XmlPullParserException, IOException {
831 final BufferedInputStream bis = new BufferedInputStream(is);
833 ShortcutUser ret = null;
834 XmlPullParser parser = Xml.newPullParser();
835 parser.setInput(bis, StandardCharsets.UTF_8.name());
838 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
839 if (type != XmlPullParser.START_TAG) {
842 final int depth = parser.getDepth();
844 final String tag = parser.getName();
846 Slog.d(TAG, String.format("depth=%d type=%d name=%s",
849 if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
850 ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
853 throwForInvalidTag(depth, tag);
858 private void scheduleSaveBaseState() {
859 scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
862 void scheduleSaveUser(@UserIdInt int userId) {
863 scheduleSaveInner(userId);
866 // In order to re-schedule, we need to reuse the same instance, so keep it in final.
867 private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo;
869 private void scheduleSaveInner(@UserIdInt int userId) {
871 Slog.d(TAG, "Scheduling to save for " + userId);
873 synchronized (mLock) {
874 if (!mDirtyUserIds.contains(userId)) {
875 mDirtyUserIds.add(userId);
878 // If already scheduled, remove that and re-schedule in N seconds.
879 mHandler.removeCallbacks(mSaveDirtyInfoRunner);
880 mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
884 void saveDirtyInfo() {
886 Slog.d(TAG, "saveDirtyInfo");
888 synchronized (mLock) {
889 for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
890 final int userId = mDirtyUserIds.get(i);
891 if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
892 saveBaseStateLocked();
894 saveUserLocked(userId);
897 mDirtyUserIds.clear();
901 /** Return the last reset time. */
902 long getLastResetTimeLocked() {
904 return mRawLastResetTime;
907 /** Return the next reset time. */
908 long getNextResetTimeLocked() {
910 return mRawLastResetTime + mResetInterval;
913 static boolean isClockValid(long time) {
914 return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
918 * Update the last reset time.
920 private void updateTimesLocked() {
922 final long now = injectCurrentTimeMillis();
924 final long prevLastResetTime = mRawLastResetTime;
926 if (mRawLastResetTime == 0) { // first launch.
928 mRawLastResetTime = now;
929 } else if (now < mRawLastResetTime) {
931 if (isClockValid(now)) {
932 Slog.w(TAG, "Clock rewound");
934 mRawLastResetTime = now;
937 if ((mRawLastResetTime + mResetInterval) <= now) {
938 final long offset = mRawLastResetTime % mResetInterval;
939 mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
942 if (prevLastResetTime != mRawLastResetTime) {
943 scheduleSaveBaseState();
949 private boolean isUserLoadedLocked(@UserIdInt int userId) {
950 return mUsers.get(userId) != null;
953 /** Return the per-user state. */
956 ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
957 ShortcutUser userPackages = mUsers.get(userId);
958 if (userPackages == null) {
959 userPackages = loadUserLocked(userId);
960 if (userPackages == null) {
961 userPackages = new ShortcutUser(userId);
963 mUsers.put(userId, userPackages);
968 void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
969 for (int i = mUsers.size() - 1; i >= 0; i--) {
970 c.accept(mUsers.valueAt(i));
974 /** Return the per-user per-package state. */
977 ShortcutPackage getPackageShortcutsLocked(
978 @NonNull String packageName, @UserIdInt int userId) {
979 return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
984 ShortcutLauncher getLauncherShortcutsLocked(
985 @NonNull String packageName, @UserIdInt int ownerUserId,
986 @UserIdInt int launcherUserId) {
987 return getUserShortcutsLocked(ownerUserId)
988 .getLauncherShortcuts(this, packageName, launcherUserId);
991 // === Caller validation ===
993 void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
994 if (shortcut.getBitmapPath() != null) {
996 Slog.d(TAG, "Removing " + shortcut.getBitmapPath());
998 new File(shortcut.getBitmapPath()).delete();
1000 shortcut.setBitmapPath(null);
1001 shortcut.setIconResourceId(0);
1002 shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
1006 public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
1007 final File packagePath = new File(getUserBitmapFilePath(userId), packageName);
1008 if (!packagePath.isDirectory()) {
1011 if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) {
1012 Slog.w(TAG, "Unable to remove directory " + packagePath);
1017 static class FileOutputStreamWithPath extends FileOutputStream {
1018 private final File mFile;
1020 public FileOutputStreamWithPath(File file) throws FileNotFoundException {
1025 public File getFile() {
1031 * Build the cached bitmap filename for a shortcut icon.
1033 * The filename will be based on the ID, except certain characters will be escaped.
1036 FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
1037 throws IOException {
1038 final File packagePath = new File(getUserBitmapFilePath(userId),
1039 shortcut.getPackageName());
1040 if (!packagePath.isDirectory()) {
1041 packagePath.mkdirs();
1042 if (!packagePath.isDirectory()) {
1043 throw new IOException("Unable to create directory " + packagePath);
1045 SELinux.restorecon(packagePath);
1048 final String baseName = String.valueOf(injectCurrentTimeMillis());
1049 for (int suffix = 0;; suffix++) {
1050 final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
1051 final File file = new File(packagePath, filename);
1052 if (!file.exists()) {
1054 Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
1056 return new FileOutputStreamWithPath(file);
1061 void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) {
1062 if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
1066 final long token = injectClearCallingIdentity();
1068 // Clear icon info on the shortcut.
1069 shortcut.setIconResourceId(0);
1070 shortcut.setBitmapPath(null);
1072 final Icon icon = shortcut.getIcon();
1074 return; // has no icon
1078 Bitmap bitmapToRecycle = null;
1080 switch (icon.getType()) {
1081 case Icon.TYPE_RESOURCE: {
1082 injectValidateIconResPackage(shortcut, icon);
1084 shortcut.setIconResourceId(icon.getResId());
1085 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
1088 case Icon.TYPE_BITMAP: {
1089 bitmap = icon.getBitmap(); // Don't recycle in this case.
1093 // This shouldn't happen because we've already validated the icon, but
1095 throw ShortcutInfo.getInvalidIconException();
1097 if (bitmap == null) {
1098 Slog.e(TAG, "Null bitmap detected");
1101 // Shrink and write to the file.
1104 final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut);
1106 path = out.getFile();
1108 Bitmap shrunk = shrinkBitmap(bitmap, mMaxIconDimension);
1110 shrunk.compress(mIconPersistFormat, mIconPersistQuality, out);
1112 if (bitmap != shrunk) {
1117 shortcut.setBitmapPath(out.getFile().getAbsolutePath());
1118 shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
1120 IoUtils.closeQuietly(out);
1122 } catch (IOException|RuntimeException e) {
1123 // STOPSHIP Change wtf to e
1124 Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
1125 if (path != null && path.exists()) {
1130 if (bitmapToRecycle != null) {
1131 bitmapToRecycle.recycle();
1133 // Once saved, we won't use the original icon information, so null it out.
1134 shortcut.clearIcon();
1137 injectRestoreCallingIdentity(token);
1141 // Unfortunately we can't do this check in unit tests because we fake creator package names,
1142 // so override in unit tests.
1143 // TODO CTS this case.
1144 void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
1145 if (!shortcut.getPackageName().equals(icon.getResPackage())) {
1146 throw new IllegalArgumentException(
1147 "Icon resource must reside in shortcut owner package");
1152 static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
1153 // Original width/height.
1154 final int ow = in.getWidth();
1155 final int oh = in.getHeight();
1156 if ((ow <= maxSize) && (oh <= maxSize)) {
1158 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
1162 final int longerDimension = Math.max(ow, oh);
1164 // New width and height.
1165 final int nw = ow * maxSize / longerDimension;
1166 final int nh = oh * maxSize / longerDimension;
1168 Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
1172 final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
1173 final Canvas c = new Canvas(scaledBitmap);
1175 final RectF dst = new RectF(0, 0, nw, nh);
1177 c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
1179 return scaledBitmap;
1182 // === Caller validation ===
1184 private boolean isCallerSystem() {
1185 final int callingUid = injectBinderCallingUid();
1186 return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
1189 private boolean isCallerShell() {
1190 final int callingUid = injectBinderCallingUid();
1191 return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
1194 private void enforceSystemOrShell() {
1195 Preconditions.checkState(isCallerSystem() || isCallerShell(),
1196 "Caller must be system or shell");
1199 private void enforceShell() {
1200 Preconditions.checkState(isCallerShell(), "Caller must be shell");
1203 private void enforceSystem() {
1204 Preconditions.checkState(isCallerSystem(), "Caller must be system");
1207 private void enforceResetThrottlingPermission() {
1208 if (isCallerSystem()) {
1211 injectEnforceCallingPermission(
1212 android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null);
1216 * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse
1217 * mockito. So instead we extracted it here and override it in the tests.
1220 void injectEnforceCallingPermission(
1221 @NonNull String permission, @Nullable String message) {
1222 mContext.enforceCallingPermission(permission, message);
1225 private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
1226 Preconditions.checkStringNotEmpty(packageName, "packageName");
1228 if (isCallerSystem()) {
1232 final int callingUid = injectBinderCallingUid();
1234 // Otherwise, make sure the arguments are valid.
1235 if (UserHandle.getUserId(callingUid) != userId) {
1236 throw new SecurityException("Invalid user-ID");
1238 if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) {
1239 return; // Caller is valid.
1241 throw new SecurityException("Calling package name mismatch");
1244 void postToHandler(Runnable r) {
1249 * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
1251 void enforceMaxDynamicShortcuts(int numShortcuts) {
1252 if (numShortcuts > mMaxDynamicShortcuts) {
1253 throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
1258 * - Sends a notification to LauncherApps
1261 void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) {
1263 Slog.d(TAG, String.format(
1264 "Shortcut changes: package=%s, user=%d", packageName, userId));
1266 notifyListeners(packageName, userId);
1267 scheduleSaveUser(userId);
1270 private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
1271 if (!mUserManager.isUserRunning(userId)) {
1274 postToHandler(() -> {
1275 final ArrayList<ShortcutChangeListener> copy;
1276 synchronized (mLock) {
1277 copy = new ArrayList<>(mListeners);
1279 // Note onShortcutChanged() needs to be called with the system service permissions.
1280 for (int i = copy.size() - 1; i >= 0; i--) {
1281 copy.get(i).onShortcutChanged(packageName, userId);
1287 * Clean up / validate an incoming shortcut.
1288 * - Make sure all mandatory fields are set.
1289 * - Make sure the intent's extras are persistable, and them to set
1290 * {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras.
1293 * TODO Detailed unit tests
1295 private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
1296 Preconditions.checkNotNull(shortcut, "Null shortcut detected");
1297 if (shortcut.getActivityComponent() != null) {
1298 Preconditions.checkState(
1299 shortcut.getPackageName().equals(
1300 shortcut.getActivityComponent().getPackageName()),
1301 "Activity package name mismatch");
1305 shortcut.enforceMandatoryFields();
1307 if (shortcut.getIcon() != null) {
1308 ShortcutInfo.validateIcon(shortcut.getIcon());
1311 validateForXml(shortcut.getId());
1312 validateForXml(shortcut.getTitle());
1313 validatePersistableBundleForXml(shortcut.getIntentPersistableExtras());
1314 validatePersistableBundleForXml(shortcut.getExtras());
1316 shortcut.replaceFlags(0);
1319 // KXmlSerializer is strict and doesn't allow certain characters, so we disallow those
1322 private static void validatePersistableBundleForXml(PersistableBundle b) {
1323 if (b == null || b.size() == 0) {
1326 for (String key : b.keySet()) {
1327 validateForXml(key);
1328 final Object value = b.get(key);
1329 if (value == null) {
1331 } else if (value instanceof String) {
1332 validateForXml((String) value);
1333 } else if (value instanceof String[]) {
1334 for (String v : (String[]) value) {
1337 } else if (value instanceof PersistableBundle) {
1338 validatePersistableBundleForXml((PersistableBundle) value);
1343 private static void validateForXml(String s) {
1344 if (TextUtils.isEmpty(s)) {
1347 for (int i = s.length() - 1; i >= 0; i--) {
1348 if (!isAllowedInXml(s.charAt(i))) {
1349 throw new IllegalArgumentException("Unsupported character detected in: " + s);
1354 private static boolean isAllowedInXml(char c) {
1355 return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
1361 public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1362 @UserIdInt int userId) {
1363 verifyCaller(packageName, userId);
1365 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1366 final int size = newShortcuts.size();
1368 synchronized (mLock) {
1369 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1372 if (!ps.tryApiCall(this)) {
1375 enforceMaxDynamicShortcuts(size);
1377 // Validate the shortcuts.
1378 for (int i = 0; i < size; i++) {
1379 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
1382 // First, remove all un-pinned; dynamic shortcuts
1383 ps.deleteAllDynamicShortcuts(this);
1385 // Then, add/update all. We need to make sure to take over "pinned" flag.
1386 for (int i = 0; i < size; i++) {
1387 final ShortcutInfo newShortcut = newShortcuts.get(i);
1388 ps.addDynamicShortcut(this, newShortcut);
1391 packageShortcutsChanged(packageName, userId);
1396 public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1397 @UserIdInt int userId) {
1398 verifyCaller(packageName, userId);
1400 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1401 final int size = newShortcuts.size();
1403 synchronized (mLock) {
1404 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1407 if (!ps.tryApiCall(this)) {
1411 for (int i = 0; i < size; i++) {
1412 final ShortcutInfo source = newShortcuts.get(i);
1413 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
1415 final ShortcutInfo target = ps.findShortcutById(source.getId());
1416 if (target != null) {
1417 final boolean replacingIcon = (source.getIcon() != null);
1418 if (replacingIcon) {
1419 removeIcon(userId, target);
1422 target.copyNonNullFieldsFrom(source);
1424 if (replacingIcon) {
1425 saveIconAndFixUpShortcut(userId, target);
1430 packageShortcutsChanged(packageName, userId);
1436 public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1437 @UserIdInt int userId) {
1438 verifyCaller(packageName, userId);
1440 final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1441 final int size = newShortcuts.size();
1443 synchronized (mLock) {
1444 final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1447 if (!ps.tryApiCall(this)) {
1450 for (int i = 0; i < size; i++) {
1451 final ShortcutInfo newShortcut = newShortcuts.get(i);
1453 // Validate the shortcut.
1454 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
1457 ps.addDynamicShortcut(this, newShortcut);
1460 packageShortcutsChanged(packageName, userId);
1466 public void removeDynamicShortcuts(String packageName, List shortcutIds,
1467 @UserIdInt int userId) {
1468 verifyCaller(packageName, userId);
1469 Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1471 synchronized (mLock) {
1472 for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1473 getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this,
1474 Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
1477 packageShortcutsChanged(packageName, userId);
1481 public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
1482 verifyCaller(packageName, userId);
1484 synchronized (mLock) {
1485 getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
1487 packageShortcutsChanged(packageName, userId);
1491 public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
1492 @UserIdInt int userId) {
1493 verifyCaller(packageName, userId);
1494 synchronized (mLock) {
1495 return getShortcutsWithQueryLocked(
1496 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1497 ShortcutInfo::isDynamic);
1502 public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
1503 @UserIdInt int userId) {
1504 verifyCaller(packageName, userId);
1505 synchronized (mLock) {
1506 return getShortcutsWithQueryLocked(
1507 packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1508 ShortcutInfo::isPinned);
1512 private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
1513 @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
1515 final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1517 getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags);
1519 return new ParceledListSlice<>(ret);
1523 public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId)
1524 throws RemoteException {
1525 verifyCaller(packageName, userId);
1527 return mMaxDynamicShortcuts;
1531 public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
1532 verifyCaller(packageName, userId);
1534 synchronized (mLock) {
1535 return mMaxUpdatesPerInterval
1536 - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
1541 public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
1542 verifyCaller(packageName, userId);
1544 synchronized (mLock) {
1545 return getNextResetTimeLocked();
1550 public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
1551 verifyCaller(packageName, userId);
1553 synchronized (mLock) {
1554 return mMaxIconDimension;
1559 * Reset all throttling, for developer options and command line. Only system/shell can call it.
1562 public void resetThrottling() {
1563 enforceSystemOrShell();
1565 resetThrottlingInner(getCallingUserId());
1568 void resetThrottlingInner(@UserIdInt int userId) {
1569 synchronized (mLock) {
1570 getUserShortcutsLocked(userId).resetThrottling();
1572 scheduleSaveUser(userId);
1573 Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId);
1576 void resetAllThrottlingInner() {
1577 synchronized (mLock) {
1578 mRawLastResetTime = injectCurrentTimeMillis();
1580 scheduleSaveBaseState();
1581 Slog.i(TAG, "ShortcutManager: throttling counter reset for all users");
1584 void resetPackageThrottling(String packageName, int userId) {
1585 synchronized (mLock) {
1586 getPackageShortcutsLocked(packageName, userId)
1587 .resetRateLimitingForCommandLineNoSaving();
1588 saveUserLocked(userId);
1593 public void onApplicationActive(String packageName, int userId) {
1595 Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId);
1597 enforceResetThrottlingPermission();
1598 resetPackageThrottling(packageName, userId);
1601 // We override this method in unit tests to do a simpler check.
1602 boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
1603 return hasShortcutHostPermissionInner(callingPackage, userId);
1606 // This method is extracted so we can directly call this method from unit tests,
1607 // even when hasShortcutPermission() is overridden.
1609 boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
1610 synchronized (mLock) {
1611 final long start = System.currentTimeMillis();
1613 final ShortcutUser user = getUserShortcutsLocked(userId);
1615 final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
1617 // Default launcher from package manager.
1618 final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
1619 final ComponentName defaultLauncher = injectPackageManagerInternal()
1620 .getHomeActivitiesAsUser(allHomeCandidates, userId);
1621 logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
1623 ComponentName detected;
1624 if (defaultLauncher != null) {
1625 detected = defaultLauncher;
1627 Slog.v(TAG, "Default launcher from PM: " + detected);
1630 detected = user.getLauncherComponent();
1632 // TODO: Make sure it's still enabled.
1634 Slog.v(TAG, "Cached launcher: " + detected);
1638 if (detected == null) {
1639 // If we reach here, that means it's the first check since the user was created,
1640 // and there's already multiple launchers and there's no default set.
1641 // Find the system one with the highest priority.
1642 // (We need to check the priority too because of FallbackHome in Settings.)
1643 // If there's no system launcher yet, then no one can access shortcuts, until
1644 // the user explicitly
1645 final int size = allHomeCandidates.size();
1647 int lastPriority = Integer.MIN_VALUE;
1648 for (int i = 0; i < size; i++) {
1649 final ResolveInfo ri = allHomeCandidates.get(i);
1650 if (!ri.activityInfo.applicationInfo.isSystemApp()) {
1654 Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
1655 ri.activityInfo.getComponentName(), ri.priority));
1657 if (ri.priority < lastPriority) {
1660 detected = ri.activityInfo.getComponentName();
1661 lastPriority = ri.priority;
1664 logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
1666 if (detected != null) {
1668 Slog.v(TAG, "Detected launcher: " + detected);
1670 user.setLauncherComponent(this, detected);
1671 return detected.getPackageName().equals(callingPackage);
1673 // Default launcher not found.
1679 // === House keeping ===
1681 private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId) {
1682 synchronized (mLock) {
1683 forEachLoadedUserLocked(user ->
1684 cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
1689 * Remove all the information associated with a package. This will really remove all the
1690 * information, including the restore information (i.e. it'll remove packages even if they're
1693 * This is called when an app is uninstalled, or an app gets "clear data"ed.
1696 void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
1697 final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
1699 final ShortcutUser user = getUserShortcutsLocked(owningUserId);
1700 boolean doNotify = false;
1702 // First, remove the package from the package list (if the package is a publisher).
1703 if (packageUserId == owningUserId) {
1704 if (user.removePackage(this, packageName) != null) {
1709 // Also remove from the launcher list (if the package is a launcher).
1710 user.removeLauncher(packageUserId, packageName);
1712 // Then remove pinned shortcuts from all launchers.
1713 user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId));
1715 // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous
1716 // step. Remove them too.
1717 user.forAllPackages(p -> p.refreshPinnedFlags(this));
1719 scheduleSaveUser(owningUserId);
1722 notifyListeners(packageName, owningUserId);
1725 if (!wasUserLoaded) {
1726 // Note this will execute the scheduled save.
1727 unloadUserLocked(owningUserId);
1732 * Entry point from {@link LauncherApps}.
1734 private class LocalService extends ShortcutServiceInternal {
1737 public List<ShortcutInfo> getShortcuts(int launcherUserId,
1738 @NonNull String callingPackage, long changedSince,
1739 @Nullable String packageName, @Nullable List<String> shortcutIds,
1740 @Nullable ComponentName componentName,
1741 int queryFlags, int userId) {
1742 final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1743 final int cloneFlag =
1744 ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0)
1745 ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER
1746 : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
1747 if (packageName == null) {
1748 shortcutIds = null; // LauncherAppsService already threw for it though.
1751 synchronized (mLock) {
1752 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1753 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1755 if (packageName != null) {
1756 getShortcutsInnerLocked(launcherUserId,
1757 callingPackage, packageName, shortcutIds, changedSince,
1758 componentName, queryFlags, userId, ret, cloneFlag);
1760 final List<String> shortcutIdsF = shortcutIds;
1761 getUserShortcutsLocked(userId).forAllPackages(p -> {
1762 getShortcutsInnerLocked(launcherUserId,
1763 callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
1764 componentName, queryFlags, userId, ret, cloneFlag);
1771 private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
1772 @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
1773 @Nullable ComponentName componentName, int queryFlags,
1774 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
1775 final ArraySet<String> ids = shortcutIds == null ? null
1776 : new ArraySet<>(shortcutIds);
1778 getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
1779 (ShortcutInfo si) -> {
1780 if (si.getLastChangedTimestamp() < changedSince) {
1783 if (ids != null && !ids.contains(si.getId())) {
1786 if (componentName != null
1787 && !componentName.equals(si.getActivityComponent())) {
1790 final boolean matchDynamic =
1791 ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
1793 final boolean matchPinned =
1794 ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
1796 return matchDynamic || matchPinned;
1797 }, cloneFlag, callingPackage, launcherUserId);
1801 public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
1802 @NonNull String packageName, @NonNull String shortcutId, int userId) {
1803 Preconditions.checkStringNotEmpty(packageName, "packageName");
1804 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
1806 synchronized (mLock) {
1807 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1808 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1810 final ShortcutInfo si = getShortcutInfoLocked(
1811 launcherUserId, callingPackage, packageName, shortcutId, userId);
1812 return si != null && si.isPinned();
1816 private ShortcutInfo getShortcutInfoLocked(
1817 int launcherUserId, @NonNull String callingPackage,
1818 @NonNull String packageName, @NonNull String shortcutId, int userId) {
1819 Preconditions.checkStringNotEmpty(packageName, "packageName");
1820 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
1822 final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
1823 getPackageShortcutsLocked(packageName, userId).findAll(
1824 ShortcutService.this, list,
1825 (ShortcutInfo si) -> shortcutId.equals(si.getId()),
1826 /* clone flags=*/ 0, callingPackage, launcherUserId);
1827 return list.size() == 0 ? null : list.get(0);
1831 public void pinShortcuts(int launcherUserId,
1832 @NonNull String callingPackage, @NonNull String packageName,
1833 @NonNull List<String> shortcutIds, int userId) {
1834 // Calling permission must be checked by LauncherAppsImpl.
1835 Preconditions.checkStringNotEmpty(packageName, "packageName");
1836 Preconditions.checkNotNull(shortcutIds, "shortcutIds");
1838 synchronized (mLock) {
1839 final ShortcutLauncher launcher =
1840 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
1841 launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
1843 launcher.pinShortcuts(
1844 ShortcutService.this, userId, packageName, shortcutIds);
1846 packageShortcutsChanged(packageName, userId);
1850 public Intent createShortcutIntent(int launcherUserId,
1851 @NonNull String callingPackage,
1852 @NonNull String packageName, @NonNull String shortcutId, int userId) {
1853 // Calling permission must be checked by LauncherAppsImpl.
1854 Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
1855 Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
1857 synchronized (mLock) {
1858 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1859 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1861 // Make sure the shortcut is actually visible to the launcher.
1862 final ShortcutInfo si = getShortcutInfoLocked(
1863 launcherUserId, callingPackage, packageName, shortcutId, userId);
1864 // "si == null" should suffice here, but check the flags too just to make sure.
1865 if (si == null || !(si.isDynamic() || si.isPinned())) {
1868 return si.getIntent();
1873 public void addListener(@NonNull ShortcutChangeListener listener) {
1874 synchronized (mLock) {
1875 mListeners.add(Preconditions.checkNotNull(listener));
1880 public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
1881 @NonNull String packageName, @NonNull String shortcutId, int userId) {
1882 Preconditions.checkNotNull(callingPackage, "callingPackage");
1883 Preconditions.checkNotNull(packageName, "packageName");
1884 Preconditions.checkNotNull(shortcutId, "shortcutId");
1886 synchronized (mLock) {
1887 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1888 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1890 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1891 packageName, userId).findShortcutById(shortcutId);
1892 return (shortcutInfo != null && shortcutInfo.hasIconResource())
1893 ? shortcutInfo.getIconResourceId() : 0;
1898 public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
1899 @NonNull String callingPackage, @NonNull String packageName,
1900 @NonNull String shortcutId, int userId) {
1901 Preconditions.checkNotNull(callingPackage, "callingPackage");
1902 Preconditions.checkNotNull(packageName, "packageName");
1903 Preconditions.checkNotNull(shortcutId, "shortcutId");
1905 synchronized (mLock) {
1906 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
1907 .attemptToRestoreIfNeededAndSave(ShortcutService.this);
1909 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1910 packageName, userId).findShortcutById(shortcutId);
1911 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
1915 if (shortcutInfo.getBitmapPath() == null) {
1916 Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
1919 return ParcelFileDescriptor.open(
1920 new File(shortcutInfo.getBitmapPath()),
1921 ParcelFileDescriptor.MODE_READ_ONLY);
1922 } catch (FileNotFoundException e) {
1923 Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath());
1930 public boolean hasShortcutHostPermission(int launcherUserId,
1931 @NonNull String callingPackage) {
1932 return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
1936 * Called by AM when the system locale changes *within the AM lock. ABSOLUTELY do not take
1937 * any locks in this method.
1940 public void onSystemLocaleChangedNoLock() {
1941 if (!FEATURE_ENABLED) {
1944 // DO NOT HOLD ANY LOCKS HERE.
1946 // We want to reset throttling for all packages for all users. But we can't just do so
1948 // - We can't load/save users that are locked.
1949 // - Even for loaded users, resetting the counters would require us to hold mLock.
1951 // So we use a "pull" model instead. In here, we just increment the "locale change
1952 // sequence number". Each ShortcutUser has the "last known locale change sequence".
1954 // This allows ShortcutUser's to detect the system locale change, so they can reset
1956 mLocaleChangeSequenceNumber.incrementAndGet();
1957 postToHandler(() -> scheduleSaveBaseState());
1962 * Package event callbacks.
1965 final PackageMonitor mPackageMonitor = new PackageMonitor() {
1967 public void onPackageAdded(String packageName, int uid) {
1968 handlePackageAdded(packageName, getChangingUserId());
1972 public void onPackageUpdateFinished(String packageName, int uid) {
1973 handlePackageUpdateFinished(packageName, getChangingUserId());
1977 public void onPackageRemoved(String packageName, int uid) {
1978 handlePackageRemoved(packageName, getChangingUserId());
1982 public void onPackageDataCleared(String packageName, int uid) {
1983 handlePackageDataCleared(packageName, getChangingUserId());
1988 * Called when a user is unlocked.
1989 * - Check all known packages still exist, and otherwise perform cleanup.
1990 * - If a package still exists, check the version code. If it's been updated, may need to
1991 * update timestamps of its shortcuts.
1994 void checkPackageChanges(@UserIdInt int ownerUserId) {
1996 Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
1998 final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
2000 synchronized (mLock) {
2001 final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
2003 user.forAllPackageItems(spi -> {
2004 if (spi.getPackageInfo().isShadow()) {
2005 return; // Don't delete shadow information.
2007 final int versionCode = getApplicationVersionCode(
2008 spi.getPackageName(), spi.getPackageUserId());
2009 if (versionCode >= 0) {
2010 // Package still installed, see if it's updated.
2011 getUserShortcutsLocked(ownerUserId).handlePackageUpdated(
2012 this, spi.getPackageName(), versionCode);
2014 gonePackages.add(PackageWithUser.of(spi));
2017 if (gonePackages.size() > 0) {
2018 for (int i = gonePackages.size() - 1; i >= 0; i--) {
2019 final PackageWithUser pu = gonePackages.get(i);
2020 cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
2026 private void handlePackageAdded(String packageName, @UserIdInt int userId) {
2028 Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
2030 synchronized (mLock) {
2031 forEachLoadedUserLocked(user ->
2032 user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
2036 private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
2038 Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
2039 packageName, userId));
2041 synchronized (mLock) {
2042 forEachLoadedUserLocked(user ->
2043 user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
2045 final int versionCode = getApplicationVersionCode(packageName, userId);
2046 if (versionCode < 0) {
2047 return; // shouldn't happen
2049 getUserShortcutsLocked(userId).handlePackageUpdated(this, packageName, versionCode);
2053 private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
2055 Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
2058 cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
2061 private void handlePackageDataCleared(String packageName, int packageUserId) {
2063 Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
2066 cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
2069 // === PackageManager interaction ===
2071 PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
2072 return injectPackageInfo(packageName, userId, true);
2075 int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
2076 final long token = injectClearCallingIdentity();
2078 return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS
2080 } catch (RemoteException e) {
2081 // Shouldn't happen.
2082 Slog.wtf(TAG, "RemoteException", e);
2085 injectRestoreCallingIdentity(token);
2090 PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
2091 boolean getSignatures) {
2092 final long start = System.currentTimeMillis();
2093 final long token = injectClearCallingIdentity();
2095 return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
2096 | (getSignatures ? PackageManager.GET_SIGNATURES : 0)
2098 } catch (RemoteException e) {
2099 // Shouldn't happen.
2100 Slog.wtf(TAG, "RemoteException", e);
2103 injectRestoreCallingIdentity(token);
2106 (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
2112 ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
2113 final long start = System.currentTimeMillis();
2114 final long token = injectClearCallingIdentity();
2116 return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
2117 } catch (RemoteException e) {
2118 // Shouldn't happen.
2119 Slog.wtf(TAG, "RemoteException", e);
2122 injectRestoreCallingIdentity(token);
2124 logDurationStat(Stats.GET_APPLICATION_INFO, start);
2128 private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
2129 final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
2130 return (ai != null) && ((ai.flags & flags) == flags);
2133 boolean isPackageInstalled(String packageName, int userId) {
2134 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
2138 * @return the version code of the package, or -1 if the app is not installed.
2140 int getApplicationVersionCode(String packageName, int userId) {
2141 final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
2142 if ((ai == null) || ((ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
2145 return ai.versionCode;
2148 // === Backup & restore ===
2150 boolean shouldBackupApp(String packageName, int userId) {
2151 return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
2154 boolean shouldBackupApp(PackageInfo pi) {
2155 return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
2159 public byte[] getBackupPayload(@UserIdInt int userId) {
2162 Slog.d(TAG, "Backing up user " + userId);
2164 synchronized (mLock) {
2165 final ShortcutUser user = getUserShortcutsLocked(userId);
2167 Slog.w(TAG, "Can't backup: user not found: id=" + userId);
2171 user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
2174 final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
2176 saveUserInternalLocked(userId, os, /* forBackup */ true);
2177 } catch (XmlPullParserException|IOException e) {
2178 // Shouldn't happen.
2179 Slog.w(TAG, "Backup failed.", e);
2182 return os.toByteArray();
2187 public void applyRestore(byte[] payload, @UserIdInt int userId) {
2190 Slog.d(TAG, "Restoring user " + userId);
2192 final ShortcutUser user;
2193 final ByteArrayInputStream is = new ByteArrayInputStream(payload);
2195 user = loadUserInternal(userId, is, /* fromBackup */ true);
2196 } catch (XmlPullParserException|IOException e) {
2197 Slog.w(TAG, "Restoration failed.", e);
2200 synchronized (mLock) {
2201 mUsers.put(userId, user);
2203 // Then purge all the save images.
2204 final File bitmapPath = getUserBitmapFilePath(userId);
2205 final boolean success = FileUtils.deleteContents(bitmapPath);
2207 Slog.w(TAG, "Failed to delete " + bitmapPath);
2210 saveUserLocked(userId);
2217 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2218 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2219 != PackageManager.PERMISSION_GRANTED) {
2220 pw.println("Permission Denial: can't dump UserManager from from pid="
2221 + Binder.getCallingPid()
2222 + ", uid=" + Binder.getCallingUid()
2223 + " without permission "
2224 + android.Manifest.permission.DUMP);
2227 dumpInner(pw, args);
2231 void dumpInner(PrintWriter pw, String[] args) {
2232 synchronized (mLock) {
2233 final long now = injectCurrentTimeMillis();
2237 pw.print(formatTime(now));
2239 pw.print(" Raw last reset: [");
2240 pw.print(mRawLastResetTime);
2242 pw.print(formatTime(mRawLastResetTime));
2244 final long last = getLastResetTimeLocked();
2245 pw.print(" Last reset: [");
2248 pw.print(formatTime(last));
2250 final long next = getNextResetTimeLocked();
2251 pw.print(" Next reset: [");
2254 pw.print(formatTime(next));
2256 pw.print(" Locale change seq#: ");
2257 pw.print(mLocaleChangeSequenceNumber.get());
2260 pw.print(" Config:");
2261 pw.print(" Max icon dim: ");
2262 pw.println(mMaxIconDimension);
2263 pw.print(" Icon format: ");
2264 pw.println(mIconPersistFormat);
2265 pw.print(" Icon quality: ");
2266 pw.println(mIconPersistQuality);
2267 pw.print(" saveDelayMillis: ");
2268 pw.println(mSaveDelayMillis);
2269 pw.print(" resetInterval: ");
2270 pw.println(mResetInterval);
2271 pw.print(" maxUpdatesPerInterval: ");
2272 pw.println(mMaxUpdatesPerInterval);
2273 pw.print(" maxDynamicShortcuts: ");
2274 pw.println(mMaxDynamicShortcuts);
2277 pw.println(" Stats:");
2278 synchronized (mStatLock) {
2279 final String p = " ";
2280 dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()");
2281 dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check");
2283 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
2284 dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
2285 dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
2288 for (int i = 0; i < mUsers.size(); i++) {
2290 mUsers.valueAt(i).dump(this, pw, " ");
2294 pw.println(" UID state:");
2296 for (int i = 0; i < mUidState.size(); i++) {
2297 final int uid = mUidState.keyAt(i);
2298 final int state = mUidState.valueAt(i);
2301 pw.print(" state=");
2303 if (isProcessStateForeground(state)) {
2306 pw.print(" last FG=");
2307 pw.print(mUidLastForegroundElapsedTime.get(uid));
2313 static String formatTime(long time) {
2314 Time tobj = new Time();
2316 return tobj.format("%Y-%m-%d %H:%M:%S");
2319 private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) {
2321 final int count = mCountStats[statId];
2322 final long dur = mDurationStats[statId];
2323 pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
2325 (count == 0 ? 0 : ((double) dur) / count)));
2328 // === Shell support ===
2331 public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
2332 String[] args, ResultReceiver resultReceiver) throws RemoteException {
2336 (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
2339 static class CommandException extends Exception {
2340 public CommandException(String message) {
2346 * Handle "adb shell cmd".
2348 private class MyShellCommand extends ShellCommand {
2350 private int mUserId = UserHandle.USER_SYSTEM;
2352 private void parseOptions(boolean takeUser)
2353 throws CommandException {
2355 while ((opt = getNextOption()) != null) {
2359 mUserId = UserHandle.parseUserArg(getNextArgRequired());
2364 throw new CommandException("Unknown option: " + opt);
2370 public int onCommand(String cmd) {
2372 return handleDefaultCommands(cmd);
2374 final PrintWriter pw = getOutPrintWriter();
2377 case "reset-package-throttling":
2378 handleResetPackageThrottling();
2380 case "reset-throttling":
2381 handleResetThrottling();
2383 case "reset-all-throttling":
2384 handleResetAllThrottling();
2386 case "override-config":
2387 handleOverrideConfig();
2389 case "reset-config":
2390 handleResetConfig();
2392 case "clear-default-launcher":
2393 handleClearDefaultLauncher();
2395 case "get-default-launcher":
2396 handleGetDefaultLauncher();
2398 case "refresh-default-launcher":
2399 handleRefreshDefaultLauncher();
2404 case "clear-shortcuts":
2405 handleClearShortcuts();
2408 return handleDefaultCommands(cmd);
2410 } catch (CommandException e) {
2411 pw.println("Error: " + e.getMessage());
2414 pw.println("Success");
2419 public void onHelp() {
2420 final PrintWriter pw = getOutPrintWriter();
2421 pw.println("Usage: cmd shortcut COMMAND [options ...]");
2423 pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
2424 pw.println(" Reset throttling for a package");
2426 pw.println("cmd shortcut reset-throttling [--user USER_ID]");
2427 pw.println(" Reset throttling for all packages and users");
2429 pw.println("cmd shortcut reset-all-throttling");
2430 pw.println(" Reset the throttling state for all users");
2432 pw.println("cmd shortcut override-config CONFIG");
2433 pw.println(" Override the configuration for testing (will last until reboot)");
2435 pw.println("cmd shortcut reset-config");
2436 pw.println(" Reset the configuration set with \"update-config\"");
2438 pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
2439 pw.println(" Clear the cached default launcher");
2441 pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
2442 pw.println(" Show the cached default launcher");
2444 pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
2445 pw.println(" Refresh the cached default launcher");
2447 pw.println("cmd shortcut unload-user [--user USER_ID]");
2448 pw.println(" Unload a user from the memory");
2449 pw.println(" (This should not affect any observable behavior)");
2451 pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE");
2452 pw.println(" Remove all shortcuts from a package, including pinned shortcuts");
2456 private void handleResetThrottling() throws CommandException {
2457 parseOptions(/* takeUser =*/ true);
2459 Slog.i(TAG, "cmd: handleResetThrottling");
2461 resetThrottlingInner(mUserId);
2464 private void handleResetAllThrottling() {
2465 Slog.i(TAG, "cmd: handleResetAllThrottling");
2467 resetAllThrottlingInner();
2470 private void handleResetPackageThrottling() throws CommandException {
2471 parseOptions(/* takeUser =*/ true);
2473 final String packageName = getNextArgRequired();
2475 Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName);
2477 resetPackageThrottling(packageName, mUserId);
2480 private void handleOverrideConfig() throws CommandException {
2481 final String config = getNextArgRequired();
2483 Slog.i(TAG, "cmd: handleOverrideConfig: " + config);
2485 synchronized (mLock) {
2486 if (!updateConfigurationLocked(config)) {
2487 throw new CommandException("override-config failed. See logcat for details.");
2492 private void handleResetConfig() {
2493 Slog.i(TAG, "cmd: handleResetConfig");
2495 synchronized (mLock) {
2496 loadConfigurationLocked();
2500 private void clearLauncher() {
2501 synchronized (mLock) {
2502 getUserShortcutsLocked(mUserId).setLauncherComponent(
2503 ShortcutService.this, null);
2507 private void showLauncher() {
2508 synchronized (mLock) {
2509 // This ensures to set the cached launcher. Package name doesn't matter.
2510 hasShortcutHostPermissionInner("-", mUserId);
2512 getOutPrintWriter().println("Launcher: "
2513 + getUserShortcutsLocked(mUserId).getLauncherComponent());
2517 private void handleClearDefaultLauncher() throws CommandException {
2518 parseOptions(/* takeUser =*/ true);
2523 private void handleGetDefaultLauncher() throws CommandException {
2524 parseOptions(/* takeUser =*/ true);
2529 private void handleRefreshDefaultLauncher() throws CommandException {
2530 parseOptions(/* takeUser =*/ true);
2536 private void handleUnloadUser() throws CommandException {
2537 parseOptions(/* takeUser =*/ true);
2539 Slog.i(TAG, "cmd: handleUnloadUser: " + mUserId);
2541 ShortcutService.this.handleCleanupUser(mUserId);
2544 private void handleClearShortcuts() throws CommandException {
2545 parseOptions(/* takeUser =*/ true);
2546 final String packageName = getNextArgRequired();
2548 Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName);
2550 ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId);
2554 // === Unit test support ===
2558 long injectCurrentTimeMillis() {
2559 return System.currentTimeMillis();
2563 long injectElapsedRealtime() {
2564 return SystemClock.elapsedRealtime();
2569 int injectBinderCallingUid() {
2570 return getCallingUid();
2573 private int getCallingUserId() {
2574 return UserHandle.getUserId(injectBinderCallingUid());
2579 long injectClearCallingIdentity() {
2580 return Binder.clearCallingIdentity();
2585 void injectRestoreCallingIdentity(long token) {
2586 Binder.restoreCallingIdentity(token);
2589 final void wtf(String message) {
2590 wtf( message, /* exception= */ null);
2594 void wtf(String message, Exception e) {
2595 Slog.wtf(TAG, message, e);
2599 File injectSystemDataPath() {
2600 return Environment.getDataSystemDirectory();
2604 File injectUserDataPath(@UserIdInt int userId) {
2605 return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
2609 boolean injectIsLowRamDevice() {
2610 return ActivityManager.isLowRamDeviceStatic();
2614 void injectRegisterUidObserver(IUidObserver observer, int which) {
2616 ActivityManagerNative.getDefault().registerUidObserver(observer, which);
2617 } catch (RemoteException shouldntHappen) {
2622 PackageManagerInternal injectPackageManagerInternal() {
2623 return mPackageManagerInternal;
2626 File getUserBitmapFilePath(@UserIdInt int userId) {
2627 return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
2631 SparseArray<ShortcutUser> getShortcutsForTest() {
2636 int getMaxDynamicShortcutsForTest() {
2637 return mMaxDynamicShortcuts;
2641 int getMaxUpdatesPerIntervalForTest() {
2642 return mMaxUpdatesPerInterval;
2646 long getResetIntervalForTest() {
2647 return mResetInterval;
2651 int getMaxIconDimensionForTest() {
2652 return mMaxIconDimension;
2656 CompressFormat getIconPersistFormatForTest() {
2657 return mIconPersistFormat;
2661 int getIconPersistQualityForTest() {
2662 return mIconPersistQuality;
2666 ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
2667 synchronized (mLock) {
2668 final ShortcutUser user = mUsers.get(userId);
2669 if (user == null) return null;
2671 final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName);
2672 if (pkg == null) return null;
2674 return pkg.findShortcutById(shortcutId);