Home | History | Annotate | Download | only in pm
      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.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;
     72 
     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;
     83 
     84 import libcore.io.IoUtils;
     85 
     86 import org.xmlpull.v1.XmlPullParser;
     87 import org.xmlpull.v1.XmlPullParserException;
     88 import org.xmlpull.v1.XmlSerializer;
     89 
     90 import java.io.BufferedInputStream;
     91 import java.io.BufferedOutputStream;
     92 import java.io.ByteArrayInputStream;
     93 import java.io.ByteArrayOutputStream;
     94 import java.io.File;
     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;
    110 
    111 /**
    112  * TODO:
    113  *
    114  * - Default launcher check does take a few ms.  Worth caching.
    115  *
    116  * - Clear data -> remove all dynamic?  but not the pinned?
    117  *
    118  * - Scan and remove orphan bitmaps (just in case).
    119  *
    120  * - Detect when already registered instances are passed to APIs again, which might break
    121  *   internal bitmap handling.
    122  *
    123  * - Add more call stats.
    124  */
    125 public class ShortcutService extends IShortcutService.Stub {
    126     static final String TAG = "ShortcutService";
    127 
    128     public static final boolean FEATURE_ENABLED = false;
    129 
    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
    133 
    134     @VisibleForTesting
    135     static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
    136 
    137     @VisibleForTesting
    138     static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
    139 
    140     @VisibleForTesting
    141     static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
    142 
    143     @VisibleForTesting
    144     static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
    145 
    146     @VisibleForTesting
    147     static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
    148 
    149     @VisibleForTesting
    150     static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
    151 
    152     @VisibleForTesting
    153     static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
    154 
    155     @VisibleForTesting
    156     static final int DEFAULT_SAVE_DELAY_MS = 3000;
    157 
    158     @VisibleForTesting
    159     static final String FILENAME_BASE_STATE = "shortcut_service.xml";
    160 
    161     @VisibleForTesting
    162     static final String DIRECTORY_PER_USER = "shortcut_service";
    163 
    164     @VisibleForTesting
    165     static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
    166 
    167     static final String DIRECTORY_BITMAPS = "bitmaps";
    168 
    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";
    172 
    173     private static final String ATTR_VALUE = "value";
    174 
    175     @VisibleForTesting
    176     interface ConfigConstants {
    177         /**
    178          * Key name for the save delay, in milliseconds. (int)
    179          */
    180         String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
    181 
    182         /**
    183          * Key name for the throttling reset interval, in seconds. (long)
    184          */
    185         String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
    186 
    187         /**
    188          * Key name for the max number of modifying API calls per app for every interval. (int)
    189          */
    190         String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval";
    191 
    192         /**
    193          * Key name for the max icon dimensions in DP, for non-low-memory devices.
    194          */
    195         String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
    196 
    197         /**
    198          * Key name for the max icon dimensions in DP, for low-memory devices.
    199          */
    200         String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
    201 
    202         /**
    203          * Key name for the max dynamic shortcuts per app. (int)
    204          */
    205         String KEY_MAX_SHORTCUTS = "max_shortcuts";
    206 
    207         /**
    208          * Key name for icon compression quality, 0-100.
    209          */
    210         String KEY_ICON_QUALITY = "icon_quality";
    211 
    212         /**
    213          * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
    214          */
    215         String KEY_ICON_FORMAT = "icon_format";
    216     }
    217 
    218     final Context mContext;
    219 
    220     private final Object mLock = new Object();
    221 
    222     private final Handler mHandler;
    223 
    224     @GuardedBy("mLock")
    225     private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
    226 
    227     @GuardedBy("mLock")
    228     private long mRawLastResetTime;
    229 
    230     /**
    231      * User ID -> UserShortcuts
    232      */
    233     @GuardedBy("mLock")
    234     private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
    235 
    236     /**
    237      * Max number of dynamic shortcuts that each application can have at a time.
    238      */
    239     private int mMaxDynamicShortcuts;
    240 
    241     /**
    242      * Max number of updating API calls that each application can make during the interval.
    243      */
    244     int mMaxUpdatesPerInterval;
    245 
    246     /**
    247      * Actual throttling-reset interval.  By default it's a day.
    248      */
    249     private long mResetInterval;
    250 
    251     /**
    252      * Icon max width/height in pixels.
    253      */
    254     private int mMaxIconDimension;
    255 
    256     private CompressFormat mIconPersistFormat;
    257     private int mIconPersistQuality;
    258 
    259     private int mSaveDelayMillis;
    260 
    261     private final IPackageManager mIPackageManager;
    262     private final PackageManagerInternal mPackageManagerInternal;
    263     private final UserManager mUserManager;
    264 
    265     @GuardedBy("mLock")
    266     final SparseIntArray mUidState = new SparseIntArray();
    267 
    268     @GuardedBy("mLock")
    269     final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
    270 
    271     @GuardedBy("mLock")
    272     private List<Integer> mDirtyUserIds = new ArrayList<>();
    273 
    274     /**
    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.
    277      *
    278      * We need this mechanism because we can't do much in the locale change callback, which is
    279      * {@link ShortcutServiceInternal#onSystemLocaleChangedNoLock()}.
    280      */
    281     private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong();
    282 
    283     private static final int PACKAGE_MATCH_FLAGS =
    284             PackageManager.MATCH_DIRECT_BOOT_AWARE
    285             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
    286             | PackageManager.MATCH_UNINSTALLED_PACKAGES;
    287 
    288     // Stats
    289     @VisibleForTesting
    290     interface Stats {
    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;
    296 
    297         int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
    298     }
    299 
    300     final Object mStatLock = new Object();
    301 
    302     @GuardedBy("mStatLock")
    303     private final int[] mCountStats = new int[Stats.COUNT];
    304 
    305     @GuardedBy("mStatLock")
    306     private final long[] mDurationStats = new long[Stats.COUNT];
    307 
    308     private static final int PROCESS_STATE_FOREGROUND_THRESHOLD =
    309             ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
    310 
    311     public ShortcutService(Context context) {
    312         this(context, BackgroundThread.get().getLooper());
    313     }
    314 
    315     @VisibleForTesting
    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);
    323 
    324         if (!FEATURE_ENABLED) {
    325             return;
    326         }
    327         mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
    328 
    329         injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
    330                 | ActivityManager.UID_OBSERVER_GONE);
    331     }
    332 
    333     void logDurationStat(int statId, long start) {
    334         synchronized (mStatLock) {
    335             mCountStats[statId]++;
    336             mDurationStats[statId] += (System.currentTimeMillis() - start);
    337         }
    338     }
    339 
    340     public long getLocaleChangeSequenceNumber() {
    341         return mLocaleChangeSequenceNumber.get();
    342     }
    343 
    344     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
    345         @Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
    346             handleOnUidStateChanged(uid, procState);
    347         }
    348 
    349         @Override public void onUidGone(int uid) throws RemoteException {
    350             handleOnUidStateChanged(uid, ActivityManager.MAX_PROCESS_STATE);
    351         }
    352 
    353         @Override public void onUidActive(int uid) throws RemoteException {
    354         }
    355 
    356         @Override public void onUidIdle(int uid) throws RemoteException {
    357         }
    358     };
    359 
    360     void handleOnUidStateChanged(int uid, int procState) {
    361         if (DEBUG_PROCSTATE) {
    362             Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
    363         }
    364         synchronized (mLock) {
    365             mUidState.put(uid, procState);
    366 
    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());
    372             }
    373         }
    374     }
    375 
    376     private boolean isProcessStateForeground(int processState) {
    377         return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
    378     }
    379 
    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.
    384             return true;
    385         }
    386         return isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE));
    387     }
    388 
    389     long getUidLastForegroundElapsedTimeLocked(int uid) {
    390         return mUidLastForegroundElapsedTime.get(uid);
    391     }
    392 
    393     /**
    394      * System service lifecycle.
    395      */
    396     public static final class Lifecycle extends SystemService {
    397         final ShortcutService mService;
    398 
    399         public Lifecycle(Context context) {
    400             super(context);
    401             mService = new ShortcutService(context);
    402         }
    403 
    404         @Override
    405         public void onStart() {
    406             publishBinderService(Context.SHORTCUT_SERVICE, mService);
    407         }
    408 
    409         @Override
    410         public void onBootPhase(int phase) {
    411             mService.onBootPhase(phase);
    412         }
    413 
    414         @Override
    415         public void onCleanupUser(int userHandle) {
    416             mService.handleCleanupUser(userHandle);
    417         }
    418 
    419         @Override
    420         public void onUnlockUser(int userId) {
    421             mService.handleUnlockUser(userId);
    422         }
    423     }
    424 
    425     /** lifecycle event */
    426     void onBootPhase(int phase) {
    427         // We want to call initialize() to initialize the configurations, so we don't disable this.
    428         if (DEBUG) {
    429             Slog.d(TAG, "onBootPhase: " + phase);
    430         }
    431         switch (phase) {
    432             case SystemService.PHASE_LOCK_SETTINGS_READY:
    433                 initialize();
    434                 break;
    435         }
    436     }
    437 
    438     /** lifecycle event */
    439     void handleUnlockUser(int userId) {
    440         if (!FEATURE_ENABLED) {
    441             return;
    442         }
    443         synchronized (mLock) {
    444             // Preload
    445             getUserShortcutsLocked(userId);
    446 
    447             checkPackageChanges(userId);
    448         }
    449     }
    450 
    451     /** lifecycle event */
    452     void handleCleanupUser(int userId) {
    453         if (!FEATURE_ENABLED) {
    454             return;
    455         }
    456         synchronized (mLock) {
    457             unloadUserLocked(userId);
    458         }
    459     }
    460 
    461     private void unloadUserLocked(int userId) {
    462         if (DEBUG) {
    463             Slog.d(TAG, "unloadUserLocked: user=" + userId);
    464         }
    465         // Save all dirty information.
    466         saveDirtyInfo();
    467 
    468         // Unload
    469         mUsers.delete(userId);
    470     }
    471 
    472     /** Return the base state file name */
    473     private AtomicFile getBaseStateFile() {
    474         final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
    475         path.mkdirs();
    476         return new AtomicFile(path);
    477     }
    478 
    479     /**
    480      * Init the instance. (load the state file, etc)
    481      */
    482     private void initialize() {
    483         synchronized (mLock) {
    484             loadConfigurationLocked();
    485             loadBaseStateLocked();
    486         }
    487     }
    488 
    489     /**
    490      * Load the configuration from Settings.
    491      */
    492     private void loadConfigurationLocked() {
    493         updateConfigurationLocked(injectShortcutManagerConstants());
    494     }
    495 
    496     /**
    497      * Load the configuration from Settings.
    498      */
    499     @VisibleForTesting
    500     boolean updateConfigurationLocked(String config) {
    501         boolean result = true;
    502 
    503         final KeyValueListParser parser = new KeyValueListParser(',');
    504         try {
    505             parser.setString(config);
    506         } catch (IllegalArgumentException e) {
    507             // Failed to parse the settings string, log this and move on
    508             // with defaults.
    509             Slog.e(TAG, "Bad shortcut manager settings", e);
    510             result = false;
    511         }
    512 
    513         mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
    514                 DEFAULT_SAVE_DELAY_MS));
    515 
    516         mResetInterval = Math.max(1, parser.getLong(
    517                 ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
    518                 * 1000L);
    519 
    520         mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
    521                 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
    522 
    523         mMaxDynamicShortcuts = Math.max(0, (int) parser.getLong(
    524                 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP));
    525 
    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));
    533 
    534         mMaxIconDimension = injectDipToPixel(iconDimensionDp);
    535 
    536         mIconPersistFormat = CompressFormat.valueOf(
    537                 parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
    538 
    539         mIconPersistQuality = (int) parser.getLong(
    540                 ConfigConstants.KEY_ICON_QUALITY,
    541                 DEFAULT_ICON_PERSIST_QUALITY);
    542 
    543         return result;
    544     }
    545 
    546     @VisibleForTesting
    547     String injectShortcutManagerConstants() {
    548         return android.provider.Settings.Global.getString(
    549                 mContext.getContentResolver(),
    550                 android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
    551     }
    552 
    553     @VisibleForTesting
    554     int injectDipToPixel(int dip) {
    555         return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
    556                 mContext.getResources().getDisplayMetrics());
    557     }
    558 
    559     // === Persisting ===
    560 
    561     @Nullable
    562     static String parseStringAttribute(XmlPullParser parser, String attribute) {
    563         return parser.getAttributeValue(null, attribute);
    564     }
    565 
    566     static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) {
    567         return parseLongAttribute(parser, attribute) == 1;
    568     }
    569 
    570     static int parseIntAttribute(XmlPullParser parser, String attribute) {
    571         return (int) parseLongAttribute(parser, attribute);
    572     }
    573 
    574     static int parseIntAttribute(XmlPullParser parser, String attribute, int def) {
    575         return (int) parseLongAttribute(parser, attribute, def);
    576     }
    577 
    578     static long parseLongAttribute(XmlPullParser parser, String attribute) {
    579         return parseLongAttribute(parser, attribute, 0);
    580     }
    581 
    582     static long parseLongAttribute(XmlPullParser parser, String attribute, long def) {
    583         final String value = parseStringAttribute(parser, attribute);
    584         if (TextUtils.isEmpty(value)) {
    585             return def;
    586         }
    587         try {
    588             return Long.parseLong(value);
    589         } catch (NumberFormatException e) {
    590             Slog.e(TAG, "Error parsing long " + value);
    591             return def;
    592         }
    593     }
    594 
    595     @Nullable
    596     static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
    597         final String value = parseStringAttribute(parser, attribute);
    598         if (TextUtils.isEmpty(value)) {
    599             return null;
    600         }
    601         return ComponentName.unflattenFromString(value);
    602     }
    603 
    604     @Nullable
    605     static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
    606         final String value = parseStringAttribute(parser, attribute);
    607         if (TextUtils.isEmpty(value)) {
    608             return null;
    609         }
    610         try {
    611             return Intent.parseUri(value, /* flags =*/ 0);
    612         } catch (URISyntaxException e) {
    613             Slog.e(TAG, "Error parsing intent", e);
    614             return null;
    615         }
    616     }
    617 
    618     static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
    619         if (TextUtils.isEmpty(value)) return;
    620 
    621         out.startTag(null, tag);
    622         out.attribute(null, ATTR_VALUE, value);
    623         out.endTag(null, tag);
    624     }
    625 
    626     static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
    627         writeTagValue(out, tag, Long.toString(value));
    628     }
    629 
    630     static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
    631         if (name == null) return;
    632         writeTagValue(out, tag, name.flattenToString());
    633     }
    634 
    635     static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
    636             throws IOException, XmlPullParserException {
    637         if (bundle == null) return;
    638 
    639         out.startTag(null, tag);
    640         bundle.saveToXml(out);
    641         out.endTag(null, tag);
    642     }
    643 
    644     static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
    645         if (TextUtils.isEmpty(value)) return;
    646 
    647         out.attribute(null, name, value);
    648     }
    649 
    650     static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
    651         writeAttr(out, name, String.valueOf(value));
    652     }
    653 
    654     static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
    655         if (value) {
    656             writeAttr(out, name, "1");
    657         }
    658     }
    659 
    660     static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
    661         if (comp == null) return;
    662         writeAttr(out, name, comp.flattenToString());
    663     }
    664 
    665     static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
    666         if (intent == null) return;
    667 
    668         writeAttr(out, name, intent.toUri(/* flags =*/ 0));
    669     }
    670 
    671     @VisibleForTesting
    672     void saveBaseStateLocked() {
    673         final AtomicFile file = getBaseStateFile();
    674         if (DEBUG) {
    675             Slog.d(TAG, "Saving to " + file.getBaseFile());
    676         }
    677 
    678         FileOutputStream outs = null;
    679         try {
    680             outs = file.startWrite();
    681 
    682             // Write to XML
    683             XmlSerializer out = new FastXmlSerializer();
    684             out.setOutput(outs, StandardCharsets.UTF_8.name());
    685             out.startDocument(null, true);
    686             out.startTag(null, TAG_ROOT);
    687 
    688             // Body.
    689             writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
    690             writeTagValue(out, TAG_LOCALE_CHANGE_SEQUENCE_NUMBER,
    691                     mLocaleChangeSequenceNumber.get());
    692 
    693             // Epilogue.
    694             out.endTag(null, TAG_ROOT);
    695             out.endDocument();
    696 
    697             // Close.
    698             file.finishWrite(outs);
    699         } catch (IOException e) {
    700             Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
    701             file.failWrite(outs);
    702         }
    703     }
    704 
    705     private void loadBaseStateLocked() {
    706         mRawLastResetTime = 0;
    707 
    708         final AtomicFile file = getBaseStateFile();
    709         if (DEBUG) {
    710             Slog.d(TAG, "Loading from " + file.getBaseFile());
    711         }
    712         try (FileInputStream in = file.openRead()) {
    713             XmlPullParser parser = Xml.newPullParser();
    714             parser.setInput(in, StandardCharsets.UTF_8.name());
    715 
    716             int type;
    717             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
    718                 if (type != XmlPullParser.START_TAG) {
    719                     continue;
    720                 }
    721                 final int depth = parser.getDepth();
    722                 // Check the root tag
    723                 final String tag = parser.getName();
    724                 if (depth == 1) {
    725                     if (!TAG_ROOT.equals(tag)) {
    726                         Slog.e(TAG, "Invalid root tag: " + tag);
    727                         return;
    728                     }
    729                     continue;
    730                 }
    731                 // Assume depth == 2
    732                 switch (tag) {
    733                     case TAG_LAST_RESET_TIME:
    734                         mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
    735                         break;
    736                     case TAG_LOCALE_CHANGE_SEQUENCE_NUMBER:
    737                         mLocaleChangeSequenceNumber.set(parseLongAttribute(parser, ATTR_VALUE));
    738                         break;
    739                     default:
    740                         Slog.e(TAG, "Invalid tag: " + tag);
    741                         break;
    742                 }
    743             }
    744         } catch (FileNotFoundException e) {
    745             // Use the default
    746         } catch (IOException|XmlPullParserException e) {
    747             Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
    748 
    749             mRawLastResetTime = 0;
    750         }
    751         // Adjust the last reset time.
    752         getLastResetTimeLocked();
    753     }
    754 
    755     private void saveUserLocked(@UserIdInt int userId) {
    756         final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
    757         if (DEBUG) {
    758             Slog.d(TAG, "Saving to " + path);
    759         }
    760         path.mkdirs();
    761         final AtomicFile file = new AtomicFile(path);
    762         FileOutputStream os = null;
    763         try {
    764             os = file.startWrite();
    765 
    766             saveUserInternalLocked(userId, os, /* forBackup= */ false);
    767 
    768             file.finishWrite(os);
    769         } catch (XmlPullParserException|IOException e) {
    770             Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
    771             file.failWrite(os);
    772         }
    773     }
    774 
    775     private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
    776             boolean forBackup) throws IOException, XmlPullParserException {
    777 
    778         final BufferedOutputStream bos = new BufferedOutputStream(os);
    779 
    780         // Write to XML
    781         XmlSerializer out = new FastXmlSerializer();
    782         out.setOutput(bos, StandardCharsets.UTF_8.name());
    783         out.startDocument(null, true);
    784 
    785         getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
    786 
    787         out.endDocument();
    788 
    789         bos.flush();
    790         os.flush();
    791     }
    792 
    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));
    795     }
    796 
    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));
    799     }
    800 
    801     @Nullable
    802     private ShortcutUser loadUserLocked(@UserIdInt int userId) {
    803         final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
    804         if (DEBUG) {
    805             Slog.d(TAG, "Loading from " + path);
    806         }
    807         final AtomicFile file = new AtomicFile(path);
    808 
    809         final FileInputStream in;
    810         try {
    811             in = file.openRead();
    812         } catch (FileNotFoundException e) {
    813             if (DEBUG) {
    814                 Slog.d(TAG, "Not found " + path);
    815             }
    816             return null;
    817         }
    818         try {
    819             return loadUserInternal(userId, in, /* forBackup= */ false);
    820         } catch (IOException|XmlPullParserException e) {
    821             Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
    822             return null;
    823         } finally {
    824             IoUtils.closeQuietly(in);
    825         }
    826     }
    827 
    828     private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
    829             boolean fromBackup) throws XmlPullParserException, IOException {
    830 
    831         final BufferedInputStream bis = new BufferedInputStream(is);
    832 
    833         ShortcutUser ret = null;
    834         XmlPullParser parser = Xml.newPullParser();
    835         parser.setInput(bis, StandardCharsets.UTF_8.name());
    836 
    837         int type;
    838         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
    839             if (type != XmlPullParser.START_TAG) {
    840                 continue;
    841             }
    842             final int depth = parser.getDepth();
    843 
    844             final String tag = parser.getName();
    845             if (DEBUG_LOAD) {
    846                 Slog.d(TAG, String.format("depth=%d type=%d name=%s",
    847                         depth, type, tag));
    848             }
    849             if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
    850                 ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
    851                 continue;
    852             }
    853             throwForInvalidTag(depth, tag);
    854         }
    855         return ret;
    856     }
    857 
    858     private void scheduleSaveBaseState() {
    859         scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
    860     }
    861 
    862     void scheduleSaveUser(@UserIdInt int userId) {
    863         scheduleSaveInner(userId);
    864     }
    865 
    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;
    868 
    869     private void scheduleSaveInner(@UserIdInt int userId) {
    870         if (DEBUG) {
    871             Slog.d(TAG, "Scheduling to save for " + userId);
    872         }
    873         synchronized (mLock) {
    874             if (!mDirtyUserIds.contains(userId)) {
    875                 mDirtyUserIds.add(userId);
    876             }
    877         }
    878         // If already scheduled, remove that and re-schedule in N seconds.
    879         mHandler.removeCallbacks(mSaveDirtyInfoRunner);
    880         mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
    881     }
    882 
    883     @VisibleForTesting
    884     void saveDirtyInfo() {
    885         if (DEBUG) {
    886             Slog.d(TAG, "saveDirtyInfo");
    887         }
    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();
    893                 } else {
    894                     saveUserLocked(userId);
    895                 }
    896             }
    897             mDirtyUserIds.clear();
    898         }
    899     }
    900 
    901     /** Return the last reset time. */
    902     long getLastResetTimeLocked() {
    903         updateTimesLocked();
    904         return mRawLastResetTime;
    905     }
    906 
    907     /** Return the next reset time. */
    908     long getNextResetTimeLocked() {
    909         updateTimesLocked();
    910         return mRawLastResetTime + mResetInterval;
    911     }
    912 
    913     static boolean isClockValid(long time) {
    914         return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
    915     }
    916 
    917     /**
    918      * Update the last reset time.
    919      */
    920     private void updateTimesLocked() {
    921 
    922         final long now = injectCurrentTimeMillis();
    923 
    924         final long prevLastResetTime = mRawLastResetTime;
    925 
    926         if (mRawLastResetTime == 0) { // first launch.
    927             // TODO Randomize??
    928             mRawLastResetTime = now;
    929         } else if (now < mRawLastResetTime) {
    930             // Clock rewound.
    931             if (isClockValid(now)) {
    932                 Slog.w(TAG, "Clock rewound");
    933                 // TODO Randomize??
    934                 mRawLastResetTime = now;
    935             }
    936         } else {
    937             if ((mRawLastResetTime + mResetInterval) <= now) {
    938                 final long offset = mRawLastResetTime % mResetInterval;
    939                 mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
    940             }
    941         }
    942         if (prevLastResetTime != mRawLastResetTime) {
    943             scheduleSaveBaseState();
    944         }
    945     }
    946 
    947     @GuardedBy("mLock")
    948     @NonNull
    949     private boolean isUserLoadedLocked(@UserIdInt int userId) {
    950         return mUsers.get(userId) != null;
    951     }
    952 
    953     /** Return the per-user state. */
    954     @GuardedBy("mLock")
    955     @NonNull
    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);
    962             }
    963             mUsers.put(userId, userPackages);
    964         }
    965         return userPackages;
    966     }
    967 
    968     void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
    969         for (int i = mUsers.size() - 1; i >= 0; i--) {
    970             c.accept(mUsers.valueAt(i));
    971         }
    972     }
    973 
    974     /** Return the per-user per-package state. */
    975     @GuardedBy("mLock")
    976     @NonNull
    977     ShortcutPackage getPackageShortcutsLocked(
    978             @NonNull String packageName, @UserIdInt int userId) {
    979         return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
    980     }
    981 
    982     @GuardedBy("mLock")
    983     @NonNull
    984     ShortcutLauncher getLauncherShortcutsLocked(
    985             @NonNull String packageName, @UserIdInt int ownerUserId,
    986             @UserIdInt int launcherUserId) {
    987         return getUserShortcutsLocked(ownerUserId)
    988                 .getLauncherShortcuts(this, packageName, launcherUserId);
    989     }
    990 
    991     // === Caller validation ===
    992 
    993     void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
    994         if (shortcut.getBitmapPath() != null) {
    995             if (DEBUG) {
    996                 Slog.d(TAG, "Removing " + shortcut.getBitmapPath());
    997             }
    998             new File(shortcut.getBitmapPath()).delete();
    999 
   1000             shortcut.setBitmapPath(null);
   1001             shortcut.setIconResourceId(0);
   1002             shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
   1003         }
   1004     }
   1005 
   1006     public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
   1007         final File packagePath = new File(getUserBitmapFilePath(userId), packageName);
   1008         if (!packagePath.isDirectory()) {
   1009             return;
   1010         }
   1011         if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) {
   1012             Slog.w(TAG, "Unable to remove directory " + packagePath);
   1013         }
   1014     }
   1015 
   1016     @VisibleForTesting
   1017     static class FileOutputStreamWithPath extends FileOutputStream {
   1018         private final File mFile;
   1019 
   1020         public FileOutputStreamWithPath(File file) throws FileNotFoundException {
   1021             super(file);
   1022             mFile = file;
   1023         }
   1024 
   1025         public File getFile() {
   1026             return mFile;
   1027         }
   1028     }
   1029 
   1030     /**
   1031      * Build the cached bitmap filename for a shortcut icon.
   1032      *
   1033      * The filename will be based on the ID, except certain characters will be escaped.
   1034      */
   1035     @VisibleForTesting
   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);
   1044             }
   1045             SELinux.restorecon(packagePath);
   1046         }
   1047 
   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()) {
   1053                 if (DEBUG) {
   1054                     Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
   1055                 }
   1056                 return new FileOutputStreamWithPath(file);
   1057             }
   1058         }
   1059     }
   1060 
   1061     void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) {
   1062         if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
   1063             return;
   1064         }
   1065 
   1066         final long token = injectClearCallingIdentity();
   1067         try {
   1068             // Clear icon info on the shortcut.
   1069             shortcut.setIconResourceId(0);
   1070             shortcut.setBitmapPath(null);
   1071 
   1072             final Icon icon = shortcut.getIcon();
   1073             if (icon == null) {
   1074                 return; // has no icon
   1075             }
   1076 
   1077             Bitmap bitmap;
   1078             Bitmap bitmapToRecycle = null;
   1079             try {
   1080                 switch (icon.getType()) {
   1081                     case Icon.TYPE_RESOURCE: {
   1082                         injectValidateIconResPackage(shortcut, icon);
   1083 
   1084                         shortcut.setIconResourceId(icon.getResId());
   1085                         shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
   1086                         return;
   1087                     }
   1088                     case Icon.TYPE_BITMAP: {
   1089                         bitmap = icon.getBitmap(); // Don't recycle in this case.
   1090                         break;
   1091                     }
   1092                     default:
   1093                         // This shouldn't happen because we've already validated the icon, but
   1094                         // just in case.
   1095                         throw ShortcutInfo.getInvalidIconException();
   1096                 }
   1097                 if (bitmap == null) {
   1098                     Slog.e(TAG, "Null bitmap detected");
   1099                     return;
   1100                 }
   1101                 // Shrink and write to the file.
   1102                 File path = null;
   1103                 try {
   1104                     final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut);
   1105                     try {
   1106                         path = out.getFile();
   1107 
   1108                         Bitmap shrunk = shrinkBitmap(bitmap, mMaxIconDimension);
   1109                         try {
   1110                             shrunk.compress(mIconPersistFormat, mIconPersistQuality, out);
   1111                         } finally {
   1112                             if (bitmap != shrunk) {
   1113                                 shrunk.recycle();
   1114                             }
   1115                         }
   1116 
   1117                         shortcut.setBitmapPath(out.getFile().getAbsolutePath());
   1118                         shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
   1119                     } finally {
   1120                         IoUtils.closeQuietly(out);
   1121                     }
   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()) {
   1126                         path.delete();
   1127                     }
   1128                 }
   1129             } finally {
   1130                 if (bitmapToRecycle != null) {
   1131                     bitmapToRecycle.recycle();
   1132                 }
   1133                 // Once saved, we won't use the original icon information, so null it out.
   1134                 shortcut.clearIcon();
   1135             }
   1136         } finally {
   1137             injectRestoreCallingIdentity(token);
   1138         }
   1139     }
   1140 
   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");
   1148         }
   1149     }
   1150 
   1151     @VisibleForTesting
   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)) {
   1157             if (DEBUG) {
   1158                 Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
   1159             }
   1160             return in;
   1161         }
   1162         final int longerDimension = Math.max(ow, oh);
   1163 
   1164         // New width and height.
   1165         final int nw = ow * maxSize / longerDimension;
   1166         final int nh = oh * maxSize / longerDimension;
   1167         if (DEBUG) {
   1168             Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
   1169                     ow, oh, nw, nh));
   1170         }
   1171 
   1172         final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
   1173         final Canvas c = new Canvas(scaledBitmap);
   1174 
   1175         final RectF dst = new RectF(0, 0, nw, nh);
   1176 
   1177         c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
   1178 
   1179         return scaledBitmap;
   1180     }
   1181 
   1182     // === Caller validation ===
   1183 
   1184     private boolean isCallerSystem() {
   1185         final int callingUid = injectBinderCallingUid();
   1186          return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
   1187     }
   1188 
   1189     private boolean isCallerShell() {
   1190         final int callingUid = injectBinderCallingUid();
   1191         return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
   1192     }
   1193 
   1194     private void enforceSystemOrShell() {
   1195         Preconditions.checkState(isCallerSystem() || isCallerShell(),
   1196                 "Caller must be system or shell");
   1197     }
   1198 
   1199     private void enforceShell() {
   1200         Preconditions.checkState(isCallerShell(), "Caller must be shell");
   1201     }
   1202 
   1203     private void enforceSystem() {
   1204         Preconditions.checkState(isCallerSystem(), "Caller must be system");
   1205     }
   1206 
   1207     private void enforceResetThrottlingPermission() {
   1208         if (isCallerSystem()) {
   1209             return;
   1210         }
   1211         injectEnforceCallingPermission(
   1212                 android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null);
   1213     }
   1214 
   1215     /**
   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.
   1218      */
   1219     @VisibleForTesting
   1220     void injectEnforceCallingPermission(
   1221             @NonNull String permission, @Nullable String message) {
   1222         mContext.enforceCallingPermission(permission, message);
   1223     }
   1224 
   1225     private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
   1226         Preconditions.checkStringNotEmpty(packageName, "packageName");
   1227 
   1228         if (isCallerSystem()) {
   1229             return; // no check
   1230         }
   1231 
   1232         final int callingUid = injectBinderCallingUid();
   1233 
   1234         // Otherwise, make sure the arguments are valid.
   1235         if (UserHandle.getUserId(callingUid) != userId) {
   1236             throw new SecurityException("Invalid user-ID");
   1237         }
   1238         if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) {
   1239             return; // Caller is valid.
   1240         }
   1241         throw new SecurityException("Calling package name mismatch");
   1242     }
   1243 
   1244     void postToHandler(Runnable r) {
   1245         mHandler.post(r);
   1246     }
   1247 
   1248     /**
   1249      * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
   1250      */
   1251     void enforceMaxDynamicShortcuts(int numShortcuts) {
   1252         if (numShortcuts > mMaxDynamicShortcuts) {
   1253             throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
   1254         }
   1255     }
   1256 
   1257     /**
   1258      * - Sends a notification to LauncherApps
   1259      * - Write to file
   1260      */
   1261     void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) {
   1262         if (DEBUG) {
   1263             Slog.d(TAG, String.format(
   1264                     "Shortcut changes: package=%s, user=%d", packageName, userId));
   1265         }
   1266         notifyListeners(packageName, userId);
   1267         scheduleSaveUser(userId);
   1268     }
   1269 
   1270     private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
   1271         if (!mUserManager.isUserRunning(userId)) {
   1272             return;
   1273         }
   1274         postToHandler(() -> {
   1275             final ArrayList<ShortcutChangeListener> copy;
   1276             synchronized (mLock) {
   1277                 copy = new ArrayList<>(mListeners);
   1278             }
   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);
   1282             }
   1283         });
   1284     }
   1285 
   1286     /**
   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.
   1291      * - Clear flags.
   1292      *
   1293      * TODO Detailed unit tests
   1294      */
   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");
   1302         }
   1303 
   1304         if (!forUpdate) {
   1305             shortcut.enforceMandatoryFields();
   1306         }
   1307         if (shortcut.getIcon() != null) {
   1308             ShortcutInfo.validateIcon(shortcut.getIcon());
   1309         }
   1310 
   1311         validateForXml(shortcut.getId());
   1312         validateForXml(shortcut.getTitle());
   1313         validatePersistableBundleForXml(shortcut.getIntentPersistableExtras());
   1314         validatePersistableBundleForXml(shortcut.getExtras());
   1315 
   1316         shortcut.replaceFlags(0);
   1317     }
   1318 
   1319     // KXmlSerializer is strict and doesn't allow certain characters, so we disallow those
   1320     // characters.
   1321 
   1322     private static void validatePersistableBundleForXml(PersistableBundle b) {
   1323         if (b == null || b.size() == 0) {
   1324             return;
   1325         }
   1326         for (String key : b.keySet()) {
   1327             validateForXml(key);
   1328             final Object value = b.get(key);
   1329             if (value == null) {
   1330                 continue;
   1331             } else if (value instanceof String) {
   1332                 validateForXml((String) value);
   1333             } else if (value instanceof String[]) {
   1334                 for (String v : (String[]) value) {
   1335                     validateForXml(v);
   1336                 }
   1337             } else if (value instanceof PersistableBundle) {
   1338                 validatePersistableBundleForXml((PersistableBundle) value);
   1339             }
   1340         }
   1341     }
   1342 
   1343     private static void validateForXml(String s) {
   1344         if (TextUtils.isEmpty(s)) {
   1345             return;
   1346         }
   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);
   1350             }
   1351         }
   1352     }
   1353 
   1354     private static boolean isAllowedInXml(char c) {
   1355         return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
   1356     }
   1357 
   1358     // === APIs ===
   1359 
   1360     @Override
   1361     public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
   1362             @UserIdInt int userId) {
   1363         verifyCaller(packageName, userId);
   1364 
   1365         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
   1366         final int size = newShortcuts.size();
   1367 
   1368         synchronized (mLock) {
   1369             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
   1370 
   1371             // Throttling.
   1372             if (!ps.tryApiCall(this)) {
   1373                 return false;
   1374             }
   1375             enforceMaxDynamicShortcuts(size);
   1376 
   1377             // Validate the shortcuts.
   1378             for (int i = 0; i < size; i++) {
   1379                 fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
   1380             }
   1381 
   1382             // First, remove all un-pinned; dynamic shortcuts
   1383             ps.deleteAllDynamicShortcuts(this);
   1384 
   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);
   1389             }
   1390         }
   1391         packageShortcutsChanged(packageName, userId);
   1392         return true;
   1393     }
   1394 
   1395     @Override
   1396     public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
   1397             @UserIdInt int userId) {
   1398         verifyCaller(packageName, userId);
   1399 
   1400         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
   1401         final int size = newShortcuts.size();
   1402 
   1403         synchronized (mLock) {
   1404             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
   1405 
   1406             // Throttling.
   1407             if (!ps.tryApiCall(this)) {
   1408                 return false;
   1409             }
   1410 
   1411             for (int i = 0; i < size; i++) {
   1412                 final ShortcutInfo source = newShortcuts.get(i);
   1413                 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
   1414 
   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);
   1420                     }
   1421 
   1422                     target.copyNonNullFieldsFrom(source);
   1423 
   1424                     if (replacingIcon) {
   1425                         saveIconAndFixUpShortcut(userId, target);
   1426                     }
   1427                 }
   1428             }
   1429         }
   1430         packageShortcutsChanged(packageName, userId);
   1431 
   1432         return true;
   1433     }
   1434 
   1435     @Override
   1436     public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
   1437             @UserIdInt int userId) {
   1438         verifyCaller(packageName, userId);
   1439 
   1440         final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
   1441         final int size = newShortcuts.size();
   1442 
   1443         synchronized (mLock) {
   1444             final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
   1445 
   1446             // Throttling.
   1447             if (!ps.tryApiCall(this)) {
   1448                 return false;
   1449             }
   1450             for (int i = 0; i < size; i++) {
   1451                 final ShortcutInfo newShortcut = newShortcuts.get(i);
   1452 
   1453                 // Validate the shortcut.
   1454                 fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
   1455 
   1456                 // Add it.
   1457                 ps.addDynamicShortcut(this, newShortcut);
   1458             }
   1459         }
   1460         packageShortcutsChanged(packageName, userId);
   1461 
   1462         return true;
   1463     }
   1464 
   1465     @Override
   1466     public void removeDynamicShortcuts(String packageName, List shortcutIds,
   1467             @UserIdInt int userId) {
   1468         verifyCaller(packageName, userId);
   1469         Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
   1470 
   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)));
   1475             }
   1476         }
   1477         packageShortcutsChanged(packageName, userId);
   1478     }
   1479 
   1480     @Override
   1481     public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
   1482         verifyCaller(packageName, userId);
   1483 
   1484         synchronized (mLock) {
   1485             getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
   1486         }
   1487         packageShortcutsChanged(packageName, userId);
   1488     }
   1489 
   1490     @Override
   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);
   1498         }
   1499     }
   1500 
   1501     @Override
   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);
   1509         }
   1510     }
   1511 
   1512     private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
   1513             @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
   1514 
   1515         final ArrayList<ShortcutInfo> ret = new ArrayList<>();
   1516 
   1517         getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags);
   1518 
   1519         return new ParceledListSlice<>(ret);
   1520     }
   1521 
   1522     @Override
   1523     public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId)
   1524             throws RemoteException {
   1525         verifyCaller(packageName, userId);
   1526 
   1527         return mMaxDynamicShortcuts;
   1528     }
   1529 
   1530     @Override
   1531     public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
   1532         verifyCaller(packageName, userId);
   1533 
   1534         synchronized (mLock) {
   1535             return mMaxUpdatesPerInterval
   1536                     - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
   1537         }
   1538     }
   1539 
   1540     @Override
   1541     public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
   1542         verifyCaller(packageName, userId);
   1543 
   1544         synchronized (mLock) {
   1545             return getNextResetTimeLocked();
   1546         }
   1547     }
   1548 
   1549     @Override
   1550     public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
   1551         verifyCaller(packageName, userId);
   1552 
   1553         synchronized (mLock) {
   1554             return mMaxIconDimension;
   1555         }
   1556     }
   1557 
   1558     /**
   1559      * Reset all throttling, for developer options and command line.  Only system/shell can call it.
   1560      */
   1561     @Override
   1562     public void resetThrottling() {
   1563         enforceSystemOrShell();
   1564 
   1565         resetThrottlingInner(getCallingUserId());
   1566     }
   1567 
   1568     void resetThrottlingInner(@UserIdInt int userId) {
   1569         synchronized (mLock) {
   1570             getUserShortcutsLocked(userId).resetThrottling();
   1571         }
   1572         scheduleSaveUser(userId);
   1573         Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId);
   1574     }
   1575 
   1576     void resetAllThrottlingInner() {
   1577         synchronized (mLock) {
   1578             mRawLastResetTime = injectCurrentTimeMillis();
   1579         }
   1580         scheduleSaveBaseState();
   1581         Slog.i(TAG, "ShortcutManager: throttling counter reset for all users");
   1582     }
   1583 
   1584     void resetPackageThrottling(String packageName, int userId) {
   1585         synchronized (mLock) {
   1586             getPackageShortcutsLocked(packageName, userId)
   1587                     .resetRateLimitingForCommandLineNoSaving();
   1588             saveUserLocked(userId);
   1589         }
   1590     }
   1591 
   1592     @Override
   1593     public void onApplicationActive(String packageName, int userId) {
   1594         if (DEBUG) {
   1595             Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userid=" + userId);
   1596         }
   1597         enforceResetThrottlingPermission();
   1598         resetPackageThrottling(packageName, userId);
   1599     }
   1600 
   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);
   1604     }
   1605 
   1606     // This method is extracted so we can directly call this method from unit tests,
   1607     // even when hasShortcutPermission() is overridden.
   1608     @VisibleForTesting
   1609     boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
   1610         synchronized (mLock) {
   1611             final long start = System.currentTimeMillis();
   1612 
   1613             final ShortcutUser user = getUserShortcutsLocked(userId);
   1614 
   1615             final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
   1616 
   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);
   1622 
   1623             ComponentName detected;
   1624             if (defaultLauncher != null) {
   1625                 detected = defaultLauncher;
   1626                 if (DEBUG) {
   1627                     Slog.v(TAG, "Default launcher from PM: " + detected);
   1628                 }
   1629             } else {
   1630                 detected = user.getLauncherComponent();
   1631 
   1632                 // TODO: Make sure it's still enabled.
   1633                 if (DEBUG) {
   1634                     Slog.v(TAG, "Cached launcher: " + detected);
   1635                 }
   1636             }
   1637 
   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();
   1646 
   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()) {
   1651                         continue;
   1652                     }
   1653                     if (DEBUG) {
   1654                         Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
   1655                                 ri.activityInfo.getComponentName(), ri.priority));
   1656                     }
   1657                     if (ri.priority < lastPriority) {
   1658                         continue;
   1659                     }
   1660                     detected = ri.activityInfo.getComponentName();
   1661                     lastPriority = ri.priority;
   1662                 }
   1663             }
   1664             logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
   1665 
   1666             if (detected != null) {
   1667                 if (DEBUG) {
   1668                     Slog.v(TAG, "Detected launcher: " + detected);
   1669                 }
   1670                 user.setLauncherComponent(this, detected);
   1671                 return detected.getPackageName().equals(callingPackage);
   1672             } else {
   1673                 // Default launcher not found.
   1674                 return false;
   1675             }
   1676         }
   1677     }
   1678 
   1679     // === House keeping ===
   1680 
   1681     private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId) {
   1682         synchronized (mLock) {
   1683             forEachLoadedUserLocked(user ->
   1684                     cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
   1685         }
   1686     }
   1687 
   1688     /**
   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
   1691      * shadow).
   1692      *
   1693      * This is called when an app is uninstalled, or an app gets "clear data"ed.
   1694      */
   1695     @VisibleForTesting
   1696     void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
   1697         final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
   1698 
   1699         final ShortcutUser user = getUserShortcutsLocked(owningUserId);
   1700         boolean doNotify = false;
   1701 
   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) {
   1705                 doNotify = true;
   1706             }
   1707         }
   1708 
   1709         // Also remove from the launcher list (if the package is a launcher).
   1710         user.removeLauncher(packageUserId, packageName);
   1711 
   1712         // Then remove pinned shortcuts from all launchers.
   1713         user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId));
   1714 
   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));
   1718 
   1719         scheduleSaveUser(owningUserId);
   1720 
   1721         if (doNotify) {
   1722             notifyListeners(packageName, owningUserId);
   1723         }
   1724 
   1725         if (!wasUserLoaded) {
   1726             // Note this will execute the scheduled save.
   1727             unloadUserLocked(owningUserId);
   1728         }
   1729     }
   1730 
   1731     /**
   1732      * Entry point from {@link LauncherApps}.
   1733      */
   1734     private class LocalService extends ShortcutServiceInternal {
   1735 
   1736         @Override
   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.
   1749             }
   1750 
   1751             synchronized (mLock) {
   1752                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
   1753                         .attemptToRestoreIfNeededAndSave(ShortcutService.this);
   1754 
   1755                 if (packageName != null) {
   1756                     getShortcutsInnerLocked(launcherUserId,
   1757                             callingPackage, packageName, shortcutIds, changedSince,
   1758                             componentName, queryFlags, userId, ret, cloneFlag);
   1759                 } else {
   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);
   1765                     });
   1766                 }
   1767             }
   1768             return ret;
   1769         }
   1770 
   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);
   1777 
   1778             getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
   1779                     (ShortcutInfo si) -> {
   1780                         if (si.getLastChangedTimestamp() < changedSince) {
   1781                             return false;
   1782                         }
   1783                         if (ids != null && !ids.contains(si.getId())) {
   1784                             return false;
   1785                         }
   1786                         if (componentName != null
   1787                                 && !componentName.equals(si.getActivityComponent())) {
   1788                             return false;
   1789                         }
   1790                         final boolean matchDynamic =
   1791                                 ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
   1792                                         && si.isDynamic();
   1793                         final boolean matchPinned =
   1794                                 ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
   1795                                         && si.isPinned();
   1796                         return matchDynamic || matchPinned;
   1797                     }, cloneFlag, callingPackage, launcherUserId);
   1798         }
   1799 
   1800         @Override
   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");
   1805 
   1806             synchronized (mLock) {
   1807                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
   1808                         .attemptToRestoreIfNeededAndSave(ShortcutService.this);
   1809 
   1810                 final ShortcutInfo si = getShortcutInfoLocked(
   1811                         launcherUserId, callingPackage, packageName, shortcutId, userId);
   1812                 return si != null && si.isPinned();
   1813             }
   1814         }
   1815 
   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");
   1821 
   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);
   1828         }
   1829 
   1830         @Override
   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");
   1837 
   1838             synchronized (mLock) {
   1839                 final ShortcutLauncher launcher =
   1840                         getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
   1841                 launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
   1842 
   1843                 launcher.pinShortcuts(
   1844                         ShortcutService.this, userId, packageName, shortcutIds);
   1845             }
   1846             packageShortcutsChanged(packageName, userId);
   1847         }
   1848 
   1849         @Override
   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");
   1856 
   1857             synchronized (mLock) {
   1858                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
   1859                         .attemptToRestoreIfNeededAndSave(ShortcutService.this);
   1860 
   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())) {
   1866                     return null;
   1867                 }
   1868                 return si.getIntent();
   1869             }
   1870         }
   1871 
   1872         @Override
   1873         public void addListener(@NonNull ShortcutChangeListener listener) {
   1874             synchronized (mLock) {
   1875                 mListeners.add(Preconditions.checkNotNull(listener));
   1876             }
   1877         }
   1878 
   1879         @Override
   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");
   1885 
   1886             synchronized (mLock) {
   1887                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
   1888                         .attemptToRestoreIfNeededAndSave(ShortcutService.this);
   1889 
   1890                 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
   1891                         packageName, userId).findShortcutById(shortcutId);
   1892                 return (shortcutInfo != null && shortcutInfo.hasIconResource())
   1893                         ? shortcutInfo.getIconResourceId() : 0;
   1894             }
   1895         }
   1896 
   1897         @Override
   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");
   1904 
   1905             synchronized (mLock) {
   1906                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
   1907                         .attemptToRestoreIfNeededAndSave(ShortcutService.this);
   1908 
   1909                 final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
   1910                         packageName, userId).findShortcutById(shortcutId);
   1911                 if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
   1912                     return null;
   1913                 }
   1914                 try {
   1915                     if (shortcutInfo.getBitmapPath() == null) {
   1916                         Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
   1917                         return null;
   1918                     }
   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());
   1924                     return null;
   1925                 }
   1926             }
   1927         }
   1928 
   1929         @Override
   1930         public boolean hasShortcutHostPermission(int launcherUserId,
   1931                 @NonNull String callingPackage) {
   1932             return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
   1933         }
   1934 
   1935         /**
   1936          * Called by AM when the system locale changes *within the AM lock.  ABSOLUTELY do not take
   1937          * any locks in this method.
   1938          */
   1939         @Override
   1940         public void onSystemLocaleChangedNoLock() {
   1941             if (!FEATURE_ENABLED) {
   1942                 return;
   1943             }
   1944             // DO NOT HOLD ANY LOCKS HERE.
   1945 
   1946             // We want to reset throttling for all packages for all users.  But we can't just do so
   1947             // here because:
   1948             // - We can't load/save users that are locked.
   1949             // - Even for loaded users, resetting the counters would require us to hold mLock.
   1950             //
   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".
   1953             //
   1954             // This allows ShortcutUser's to detect the system locale change, so they can reset
   1955             // counters.
   1956             mLocaleChangeSequenceNumber.incrementAndGet();
   1957             postToHandler(() -> scheduleSaveBaseState());
   1958         }
   1959     }
   1960 
   1961     /**
   1962      * Package event callbacks.
   1963      */
   1964     @VisibleForTesting
   1965     final PackageMonitor mPackageMonitor = new PackageMonitor() {
   1966         @Override
   1967         public void onPackageAdded(String packageName, int uid) {
   1968             handlePackageAdded(packageName, getChangingUserId());
   1969         }
   1970 
   1971         @Override
   1972         public void onPackageUpdateFinished(String packageName, int uid) {
   1973             handlePackageUpdateFinished(packageName, getChangingUserId());
   1974         }
   1975 
   1976         @Override
   1977         public void onPackageRemoved(String packageName, int uid) {
   1978             handlePackageRemoved(packageName, getChangingUserId());
   1979         }
   1980 
   1981         @Override
   1982         public void onPackageDataCleared(String packageName, int uid) {
   1983             handlePackageDataCleared(packageName, getChangingUserId());
   1984         }
   1985     };
   1986 
   1987     /**
   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.
   1992      */
   1993     @VisibleForTesting
   1994     void checkPackageChanges(@UserIdInt int ownerUserId) {
   1995         if (DEBUG) {
   1996             Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
   1997         }
   1998         final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
   1999 
   2000         synchronized (mLock) {
   2001             final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
   2002 
   2003             user.forAllPackageItems(spi -> {
   2004                 if (spi.getPackageInfo().isShadow()) {
   2005                     return; // Don't delete shadow information.
   2006                 }
   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);
   2013                 } else {
   2014                     gonePackages.add(PackageWithUser.of(spi));
   2015                 }
   2016             });
   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);
   2021                 }
   2022             }
   2023         }
   2024     }
   2025 
   2026     private void handlePackageAdded(String packageName, @UserIdInt int userId) {
   2027         if (DEBUG) {
   2028             Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
   2029         }
   2030         synchronized (mLock) {
   2031             forEachLoadedUserLocked(user ->
   2032                     user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
   2033         }
   2034     }
   2035 
   2036     private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
   2037         if (DEBUG) {
   2038             Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
   2039                     packageName, userId));
   2040         }
   2041         synchronized (mLock) {
   2042             forEachLoadedUserLocked(user ->
   2043                     user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
   2044 
   2045             final int versionCode = getApplicationVersionCode(packageName, userId);
   2046             if (versionCode < 0) {
   2047                 return; // shouldn't happen
   2048             }
   2049             getUserShortcutsLocked(userId).handlePackageUpdated(this, packageName, versionCode);
   2050         }
   2051     }
   2052 
   2053     private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
   2054         if (DEBUG) {
   2055             Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
   2056                     packageUserId));
   2057         }
   2058         cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
   2059     }
   2060 
   2061     private void handlePackageDataCleared(String packageName, int packageUserId) {
   2062         if (DEBUG) {
   2063             Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
   2064                     packageUserId));
   2065         }
   2066         cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
   2067     }
   2068 
   2069     // === PackageManager interaction ===
   2070 
   2071     PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
   2072         return injectPackageInfo(packageName, userId, true);
   2073     }
   2074 
   2075     int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
   2076         final long token = injectClearCallingIdentity();
   2077         try {
   2078             return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS
   2079                     , userId);
   2080         } catch (RemoteException e) {
   2081             // Shouldn't happen.
   2082             Slog.wtf(TAG, "RemoteException", e);
   2083             return -1;
   2084         } finally {
   2085             injectRestoreCallingIdentity(token);
   2086         }
   2087     }
   2088 
   2089     @VisibleForTesting
   2090     PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
   2091             boolean getSignatures) {
   2092         final long start = System.currentTimeMillis();
   2093         final long token = injectClearCallingIdentity();
   2094         try {
   2095             return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
   2096                     | (getSignatures ? PackageManager.GET_SIGNATURES : 0)
   2097                     , userId);
   2098         } catch (RemoteException e) {
   2099             // Shouldn't happen.
   2100             Slog.wtf(TAG, "RemoteException", e);
   2101             return null;
   2102         } finally {
   2103             injectRestoreCallingIdentity(token);
   2104 
   2105             logDurationStat(
   2106                     (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
   2107                     start);
   2108         }
   2109     }
   2110 
   2111     @VisibleForTesting
   2112     ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
   2113         final long start = System.currentTimeMillis();
   2114         final long token = injectClearCallingIdentity();
   2115         try {
   2116             return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
   2117         } catch (RemoteException e) {
   2118             // Shouldn't happen.
   2119             Slog.wtf(TAG, "RemoteException", e);
   2120             return null;
   2121         } finally {
   2122             injectRestoreCallingIdentity(token);
   2123 
   2124             logDurationStat(Stats.GET_APPLICATION_INFO, start);
   2125         }
   2126     }
   2127 
   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);
   2131     }
   2132 
   2133     boolean isPackageInstalled(String packageName, int userId) {
   2134         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
   2135     }
   2136 
   2137     /**
   2138      * @return the version code of the package, or -1 if the app is not installed.
   2139      */
   2140     int getApplicationVersionCode(String packageName, int userId) {
   2141         final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
   2142         if ((ai == null) || ((ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
   2143             return -1;
   2144         }
   2145         return ai.versionCode;
   2146     }
   2147 
   2148     // === Backup & restore ===
   2149 
   2150     boolean shouldBackupApp(String packageName, int userId) {
   2151         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
   2152     }
   2153 
   2154     boolean shouldBackupApp(PackageInfo pi) {
   2155         return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
   2156     }
   2157 
   2158     @Override
   2159     public byte[] getBackupPayload(@UserIdInt int userId) {
   2160         enforceSystem();
   2161         if (DEBUG) {
   2162             Slog.d(TAG, "Backing up user " + userId);
   2163         }
   2164         synchronized (mLock) {
   2165             final ShortcutUser user = getUserShortcutsLocked(userId);
   2166             if (user == null) {
   2167                 Slog.w(TAG, "Can't backup: user not found: id=" + userId);
   2168                 return null;
   2169             }
   2170 
   2171             user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
   2172 
   2173             // Then save.
   2174             final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
   2175             try {
   2176                 saveUserInternalLocked(userId, os, /* forBackup */ true);
   2177             } catch (XmlPullParserException|IOException e) {
   2178                 // Shouldn't happen.
   2179                 Slog.w(TAG, "Backup failed.", e);
   2180                 return null;
   2181             }
   2182             return os.toByteArray();
   2183         }
   2184     }
   2185 
   2186     @Override
   2187     public void applyRestore(byte[] payload, @UserIdInt int userId) {
   2188         enforceSystem();
   2189         if (DEBUG) {
   2190             Slog.d(TAG, "Restoring user " + userId);
   2191         }
   2192         final ShortcutUser user;
   2193         final ByteArrayInputStream is = new ByteArrayInputStream(payload);
   2194         try {
   2195             user = loadUserInternal(userId, is, /* fromBackup */ true);
   2196         } catch (XmlPullParserException|IOException e) {
   2197             Slog.w(TAG, "Restoration failed.", e);
   2198             return;
   2199         }
   2200         synchronized (mLock) {
   2201             mUsers.put(userId, user);
   2202 
   2203             // Then purge all the save images.
   2204             final File bitmapPath = getUserBitmapFilePath(userId);
   2205             final boolean success = FileUtils.deleteContents(bitmapPath);
   2206             if (!success) {
   2207                 Slog.w(TAG, "Failed to delete " + bitmapPath);
   2208             }
   2209 
   2210             saveUserLocked(userId);
   2211         }
   2212     }
   2213 
   2214     // === Dump ===
   2215 
   2216     @Override
   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);
   2225             return;
   2226         }
   2227         dumpInner(pw, args);
   2228     }
   2229 
   2230     @VisibleForTesting
   2231     void dumpInner(PrintWriter pw, String[] args) {
   2232         synchronized (mLock) {
   2233             final long now = injectCurrentTimeMillis();
   2234             pw.print("Now: [");
   2235             pw.print(now);
   2236             pw.print("] ");
   2237             pw.print(formatTime(now));
   2238 
   2239             pw.print("  Raw last reset: [");
   2240             pw.print(mRawLastResetTime);
   2241             pw.print("] ");
   2242             pw.print(formatTime(mRawLastResetTime));
   2243 
   2244             final long last = getLastResetTimeLocked();
   2245             pw.print("  Last reset: [");
   2246             pw.print(last);
   2247             pw.print("] ");
   2248             pw.print(formatTime(last));
   2249 
   2250             final long next = getNextResetTimeLocked();
   2251             pw.print("  Next reset: [");
   2252             pw.print(next);
   2253             pw.print("] ");
   2254             pw.print(formatTime(next));
   2255 
   2256             pw.print("  Locale change seq#: ");
   2257             pw.print(mLocaleChangeSequenceNumber.get());
   2258             pw.println();
   2259 
   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);
   2275             pw.println();
   2276 
   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");
   2282 
   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");
   2286             }
   2287 
   2288             for (int i = 0; i < mUsers.size(); i++) {
   2289                 pw.println();
   2290                 mUsers.valueAt(i).dump(this, pw, "  ");
   2291             }
   2292 
   2293             pw.println();
   2294             pw.println("  UID state:");
   2295 
   2296             for (int i = 0; i < mUidState.size(); i++) {
   2297                 final int uid = mUidState.keyAt(i);
   2298                 final int state = mUidState.valueAt(i);
   2299                 pw.print("    UID=");
   2300                 pw.print(uid);
   2301                 pw.print(" state=");
   2302                 pw.print(state);
   2303                 if (isProcessStateForeground(state)) {
   2304                     pw.print("  [FG]");
   2305                 }
   2306                 pw.print("  last FG=");
   2307                 pw.print(mUidLastForegroundElapsedTime.get(uid));
   2308                 pw.println();
   2309             }
   2310         }
   2311     }
   2312 
   2313     static String formatTime(long time) {
   2314         Time tobj = new Time();
   2315         tobj.set(time);
   2316         return tobj.format("%Y-%m-%d %H:%M:%S");
   2317     }
   2318 
   2319     private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) {
   2320         pw.print(prefix);
   2321         final int count = mCountStats[statId];
   2322         final long dur = mDurationStats[statId];
   2323         pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
   2324                 label, count, dur,
   2325                 (count == 0 ? 0 : ((double) dur) / count)));
   2326     }
   2327 
   2328     // === Shell support ===
   2329 
   2330     @Override
   2331     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
   2332             String[] args, ResultReceiver resultReceiver) throws RemoteException {
   2333 
   2334         enforceShell();
   2335 
   2336         (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
   2337     }
   2338 
   2339     static class CommandException extends Exception {
   2340         public CommandException(String message) {
   2341             super(message);
   2342         }
   2343     }
   2344 
   2345     /**
   2346      * Handle "adb shell cmd".
   2347      */
   2348     private class MyShellCommand extends ShellCommand {
   2349 
   2350         private int mUserId = UserHandle.USER_SYSTEM;
   2351 
   2352         private void parseOptions(boolean takeUser)
   2353                 throws CommandException {
   2354             String opt;
   2355             while ((opt = getNextOption()) != null) {
   2356                 switch (opt) {
   2357                     case "--user":
   2358                         if (takeUser) {
   2359                             mUserId = UserHandle.parseUserArg(getNextArgRequired());
   2360                             break;
   2361                         }
   2362                         // fallthrough
   2363                     default:
   2364                         throw new CommandException("Unknown option: " + opt);
   2365                 }
   2366             }
   2367         }
   2368 
   2369         @Override
   2370         public int onCommand(String cmd) {
   2371             if (cmd == null) {
   2372                 return handleDefaultCommands(cmd);
   2373             }
   2374             final PrintWriter pw = getOutPrintWriter();
   2375             try {
   2376                 switch (cmd) {
   2377                     case "reset-package-throttling":
   2378                         handleResetPackageThrottling();
   2379                         break;
   2380                     case "reset-throttling":
   2381                         handleResetThrottling();
   2382                         break;
   2383                     case "reset-all-throttling":
   2384                         handleResetAllThrottling();
   2385                         break;
   2386                     case "override-config":
   2387                         handleOverrideConfig();
   2388                         break;
   2389                     case "reset-config":
   2390                         handleResetConfig();
   2391                         break;
   2392                     case "clear-default-launcher":
   2393                         handleClearDefaultLauncher();
   2394                         break;
   2395                     case "get-default-launcher":
   2396                         handleGetDefaultLauncher();
   2397                         break;
   2398                     case "refresh-default-launcher":
   2399                         handleRefreshDefaultLauncher();
   2400                         break;
   2401                     case "unload-user":
   2402                         handleUnloadUser();
   2403                         break;
   2404                     case "clear-shortcuts":
   2405                         handleClearShortcuts();
   2406                         break;
   2407                     default:
   2408                         return handleDefaultCommands(cmd);
   2409                 }
   2410             } catch (CommandException e) {
   2411                 pw.println("Error: " + e.getMessage());
   2412                 return 1;
   2413             }
   2414             pw.println("Success");
   2415             return 0;
   2416         }
   2417 
   2418         @Override
   2419         public void onHelp() {
   2420             final PrintWriter pw = getOutPrintWriter();
   2421             pw.println("Usage: cmd shortcut COMMAND [options ...]");
   2422             pw.println();
   2423             pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
   2424             pw.println("    Reset throttling for a package");
   2425             pw.println();
   2426             pw.println("cmd shortcut reset-throttling [--user USER_ID]");
   2427             pw.println("    Reset throttling for all packages and users");
   2428             pw.println();
   2429             pw.println("cmd shortcut reset-all-throttling");
   2430             pw.println("    Reset the throttling state for all users");
   2431             pw.println();
   2432             pw.println("cmd shortcut override-config CONFIG");
   2433             pw.println("    Override the configuration for testing (will last until reboot)");
   2434             pw.println();
   2435             pw.println("cmd shortcut reset-config");
   2436             pw.println("    Reset the configuration set with \"update-config\"");
   2437             pw.println();
   2438             pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
   2439             pw.println("    Clear the cached default launcher");
   2440             pw.println();
   2441             pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
   2442             pw.println("    Show the cached default launcher");
   2443             pw.println();
   2444             pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
   2445             pw.println("    Refresh the cached default launcher");
   2446             pw.println();
   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)");
   2450             pw.println();
   2451             pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE");
   2452             pw.println("    Remove all shortcuts from a package, including pinned shortcuts");
   2453             pw.println();
   2454         }
   2455 
   2456         private void handleResetThrottling() throws CommandException {
   2457             parseOptions(/* takeUser =*/ true);
   2458 
   2459             Slog.i(TAG, "cmd: handleResetThrottling");
   2460 
   2461             resetThrottlingInner(mUserId);
   2462         }
   2463 
   2464         private void handleResetAllThrottling() {
   2465             Slog.i(TAG, "cmd: handleResetAllThrottling");
   2466 
   2467             resetAllThrottlingInner();
   2468         }
   2469 
   2470         private void handleResetPackageThrottling() throws CommandException {
   2471             parseOptions(/* takeUser =*/ true);
   2472 
   2473             final String packageName = getNextArgRequired();
   2474 
   2475             Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName);
   2476 
   2477             resetPackageThrottling(packageName, mUserId);
   2478         }
   2479 
   2480         private void handleOverrideConfig() throws CommandException {
   2481             final String config = getNextArgRequired();
   2482 
   2483             Slog.i(TAG, "cmd: handleOverrideConfig: " + config);
   2484 
   2485             synchronized (mLock) {
   2486                 if (!updateConfigurationLocked(config)) {
   2487                     throw new CommandException("override-config failed.  See logcat for details.");
   2488                 }
   2489             }
   2490         }
   2491 
   2492         private void handleResetConfig() {
   2493             Slog.i(TAG, "cmd: handleResetConfig");
   2494 
   2495             synchronized (mLock) {
   2496                 loadConfigurationLocked();
   2497             }
   2498         }
   2499 
   2500         private void clearLauncher() {
   2501             synchronized (mLock) {
   2502                 getUserShortcutsLocked(mUserId).setLauncherComponent(
   2503                         ShortcutService.this, null);
   2504             }
   2505         }
   2506 
   2507         private void showLauncher() {
   2508             synchronized (mLock) {
   2509                 // This ensures to set the cached launcher.  Package name doesn't matter.
   2510                 hasShortcutHostPermissionInner("-", mUserId);
   2511 
   2512                 getOutPrintWriter().println("Launcher: "
   2513                         + getUserShortcutsLocked(mUserId).getLauncherComponent());
   2514             }
   2515         }
   2516 
   2517         private void handleClearDefaultLauncher() throws CommandException {
   2518             parseOptions(/* takeUser =*/ true);
   2519 
   2520             clearLauncher();
   2521         }
   2522 
   2523         private void handleGetDefaultLauncher() throws CommandException {
   2524             parseOptions(/* takeUser =*/ true);
   2525 
   2526             showLauncher();
   2527         }
   2528 
   2529         private void handleRefreshDefaultLauncher() throws CommandException {
   2530             parseOptions(/* takeUser =*/ true);
   2531 
   2532             clearLauncher();
   2533             showLauncher();
   2534         }
   2535 
   2536         private void handleUnloadUser() throws CommandException {
   2537             parseOptions(/* takeUser =*/ true);
   2538 
   2539             Slog.i(TAG, "cmd: handleUnloadUser: " + mUserId);
   2540 
   2541             ShortcutService.this.handleCleanupUser(mUserId);
   2542         }
   2543 
   2544         private void handleClearShortcuts() throws CommandException {
   2545             parseOptions(/* takeUser =*/ true);
   2546             final String packageName = getNextArgRequired();
   2547 
   2548             Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName);
   2549 
   2550             ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId);
   2551         }
   2552     }
   2553 
   2554     // === Unit test support ===
   2555 
   2556     // Injection point.
   2557     @VisibleForTesting
   2558     long injectCurrentTimeMillis() {
   2559         return System.currentTimeMillis();
   2560     }
   2561 
   2562     @VisibleForTesting
   2563     long injectElapsedRealtime() {
   2564         return SystemClock.elapsedRealtime();
   2565     }
   2566 
   2567     // Injection point.
   2568     @VisibleForTesting
   2569     int injectBinderCallingUid() {
   2570         return getCallingUid();
   2571     }
   2572 
   2573     private int getCallingUserId() {
   2574         return UserHandle.getUserId(injectBinderCallingUid());
   2575     }
   2576 
   2577     // Injection point.
   2578     @VisibleForTesting
   2579     long injectClearCallingIdentity() {
   2580         return Binder.clearCallingIdentity();
   2581     }
   2582 
   2583     // Injection point.
   2584     @VisibleForTesting
   2585     void injectRestoreCallingIdentity(long token) {
   2586         Binder.restoreCallingIdentity(token);
   2587     }
   2588 
   2589     final void wtf(String message) {
   2590         wtf( message, /* exception= */ null);
   2591     }
   2592 
   2593     // Injection point.
   2594     void wtf(String message, Exception e) {
   2595         Slog.wtf(TAG, message, e);
   2596     }
   2597 
   2598     @VisibleForTesting
   2599     File injectSystemDataPath() {
   2600         return Environment.getDataSystemDirectory();
   2601     }
   2602 
   2603     @VisibleForTesting
   2604     File injectUserDataPath(@UserIdInt int userId) {
   2605         return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
   2606     }
   2607 
   2608     @VisibleForTesting
   2609     boolean injectIsLowRamDevice() {
   2610         return ActivityManager.isLowRamDeviceStatic();
   2611     }
   2612 
   2613     @VisibleForTesting
   2614     void injectRegisterUidObserver(IUidObserver observer, int which) {
   2615         try {
   2616             ActivityManagerNative.getDefault().registerUidObserver(observer, which);
   2617         } catch (RemoteException shouldntHappen) {
   2618         }
   2619     }
   2620 
   2621     @VisibleForTesting
   2622     PackageManagerInternal injectPackageManagerInternal() {
   2623         return mPackageManagerInternal;
   2624     }
   2625 
   2626     File getUserBitmapFilePath(@UserIdInt int userId) {
   2627         return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
   2628     }
   2629 
   2630     @VisibleForTesting
   2631     SparseArray<ShortcutUser> getShortcutsForTest() {
   2632         return mUsers;
   2633     }
   2634 
   2635     @VisibleForTesting
   2636     int getMaxDynamicShortcutsForTest() {
   2637         return mMaxDynamicShortcuts;
   2638     }
   2639 
   2640     @VisibleForTesting
   2641     int getMaxUpdatesPerIntervalForTest() {
   2642         return mMaxUpdatesPerInterval;
   2643     }
   2644 
   2645     @VisibleForTesting
   2646     long getResetIntervalForTest() {
   2647         return mResetInterval;
   2648     }
   2649 
   2650     @VisibleForTesting
   2651     int getMaxIconDimensionForTest() {
   2652         return mMaxIconDimension;
   2653     }
   2654 
   2655     @VisibleForTesting
   2656     CompressFormat getIconPersistFormatForTest() {
   2657         return mIconPersistFormat;
   2658     }
   2659 
   2660     @VisibleForTesting
   2661     int getIconPersistQualityForTest() {
   2662         return mIconPersistQuality;
   2663     }
   2664 
   2665     @VisibleForTesting
   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;
   2670 
   2671             final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName);
   2672             if (pkg == null) return null;
   2673 
   2674             return pkg.findShortcutById(shortcutId);
   2675         }
   2676     }
   2677 }
   2678