ShortcutService.java revision 9da23fc6ac565b38129d52f4f8f174c833a9bd01
19a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger/*
29a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger * Copyright (C) 2016 The Android Open Source Project
39a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger *
49a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger * Licensed under the Apache License, Version 2.0 (the "License");
59a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger * you may not use this file except in compliance with the License.
69edae346dd664f63aa4d707b9090c9638fbc9198Raymond Hettinger * You may obtain a copy of the License at
79a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger *
89a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger *      http://www.apache.org/licenses/LICENSE-2.0
99a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger *
109a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger * Unless required by applicable law or agreed to in writing, software
110ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettinger * distributed under the License is distributed on an "AS IS" BASIS,
129a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettinger * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettinger * See the License for the specific language governing permissions and
140ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettinger * limitations under the License.
150ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettinger */
160ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerpackage com.android.server.pm;
170ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettinger
180ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.annotation.NonNull;
190ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.annotation.Nullable;
200ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.annotation.UserIdInt;
210ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.app.ActivityManager;
220ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.app.AppGlobals;
230ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.ComponentName;
240ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.ContentProvider;
259a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport android.content.Context;
269a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport android.content.Intent;
279a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport android.content.pm.ApplicationInfo;
289a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport android.content.pm.IPackageManager;
290ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.IShortcutService;
300ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.LauncherApps;
310ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.LauncherApps.ShortcutQuery;
320ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.PackageInfo;
330ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.PackageManager;
340ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.PackageManagerInternal;
350ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.ParceledListSlice;
360ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.ResolveInfo;
370ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.ShortcutInfo;
380ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.ShortcutServiceInternal;
390ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
400ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.graphics.Bitmap;
410ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.graphics.Bitmap.CompressFormat;
420ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.graphics.BitmapFactory;
439a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport android.graphics.Canvas;
449a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport android.graphics.RectF;
459a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport android.graphics.drawable.Icon;
469a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport android.net.Uri;
470ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.os.Binder;
480ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.os.Environment;
490ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.os.Handler;
500ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.os.Looper;
510ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.os.ParcelFileDescriptor;
520ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.os.PersistableBundle;
530ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport android.os.Process;
54abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport android.os.RemoteException;
552c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.os.ResultReceiver;
562c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.os.SELinux;
572c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.os.ShellCommand;
582c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.os.UserHandle;
592c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.os.UserManager;
60abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport android.text.TextUtils;
612c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.text.format.Time;
622c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.util.ArrayMap;
632c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.util.ArraySet;
642c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.util.AtomicFile;
652c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.util.KeyValueListParser;
662c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.util.Slog;
672c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.util.SparseArray;
682c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport android.util.TypedValue;
69abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport android.util.Xml;
70abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwald
712c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport com.android.internal.annotations.GuardedBy;
722c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport com.android.internal.annotations.VisibleForTesting;
732c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport com.android.internal.content.PackageMonitor;
742c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport com.android.internal.os.BackgroundThread;
752c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport com.android.internal.util.FastXmlSerializer;
76abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport com.android.internal.util.Preconditions;
772c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport com.android.server.LocalServices;
782c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport com.android.server.SystemService;
792c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport com.android.server.pm.ShortcutUser.PackageWithUser;
802c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandl
812c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport libcore.io.IoUtils;
822c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandl
832c9838e30f825b3bdaf0fc33f15a3cc74320ba6eGeorg Brandlimport org.xmlpull.v1.XmlPullParser;
84abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport org.xmlpull.v1.XmlPullParserException;
850ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport org.xmlpull.v1.XmlSerializer;
860ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettinger
870ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport java.io.BufferedInputStream;
880ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport java.io.BufferedOutputStream;
890ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport java.io.ByteArrayInputStream;
900ad142aba05d0ecbbc42117e43cf98201b2cabc9Raymond Hettingerimport java.io.ByteArrayOutputStream;
919a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport java.io.File;
929a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport java.io.FileDescriptor;
939a80c5dbc497211267062d39c3fb4e2d5a8e702dRaymond Hettingerimport java.io.FileInputStream;
94abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.io.FileNotFoundException;
95abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.io.FileOutputStream;
96abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.io.IOException;
97abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.io.InputStream;
98abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.io.OutputStream;
99abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.io.PrintWriter;
100abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.net.URISyntaxException;
101abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.nio.charset.StandardCharsets;
102c7797dc7482035ee166ca2e941b623382b92e1fcSerhiy Storchakaimport java.util.ArrayList;
103abb02e59946f9ea3076e96e3b03b51d1cebd46b4Walter Dörwaldimport java.util.List;
104import java.util.function.Predicate;
105
106/**
107 * TODO:
108 *
109 * - Default launcher check does take a few ms.  Worth caching.
110 *
111 * - Clear data -> remove all dynamic?  but not the pinned?
112 *
113 * - Scan and remove orphan bitmaps (just in case).
114 *
115 * - Backup & restore
116 *
117 * - Detect when already registered instances are passed to APIs again, which might break
118 *   internal bitmap handling.
119 */
120public class ShortcutService extends IShortcutService.Stub {
121    static final String TAG = "ShortcutService";
122
123    static final boolean DEBUG = false; // STOPSHIP if true
124    static final boolean DEBUG_LOAD = false; // STOPSHIP if true
125    static final boolean ENABLE_DEBUG_COMMAND = true; // STOPSHIP if true
126
127    @VisibleForTesting
128    static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
129
130    @VisibleForTesting
131    static final int DEFAULT_MAX_DAILY_UPDATES = 10;
132
133    @VisibleForTesting
134    static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
135
136    @VisibleForTesting
137    static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
138
139    @VisibleForTesting
140    static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
141
142    @VisibleForTesting
143    static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
144
145    @VisibleForTesting
146    static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
147
148    @VisibleForTesting
149    static final int DEFAULT_SAVE_DELAY_MS = 3000;
150
151    @VisibleForTesting
152    static final String FILENAME_BASE_STATE = "shortcut_service.xml";
153
154    @VisibleForTesting
155    static final String DIRECTORY_PER_USER = "shortcut_service";
156
157    @VisibleForTesting
158    static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
159
160    static final String DIRECTORY_BITMAPS = "bitmaps";
161
162    private static final String TAG_ROOT = "root";
163    private static final String TAG_LAST_RESET_TIME = "last_reset_time";
164
165    private static final String ATTR_VALUE = "value";
166
167    @VisibleForTesting
168    interface ConfigConstants {
169        /**
170         * Key name for the save delay, in milliseconds. (int)
171         */
172        String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
173
174        /**
175         * Key name for the throttling reset interval, in seconds. (long)
176         */
177        String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
178
179        /**
180         * Key name for the max number of modifying API calls per app for every interval. (int)
181         */
182        String KEY_MAX_DAILY_UPDATES = "max_daily_updates";
183
184        /**
185         * Key name for the max icon dimensions in DP, for non-low-memory devices.
186         */
187        String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
188
189        /**
190         * Key name for the max icon dimensions in DP, for low-memory devices.
191         */
192        String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
193
194        /**
195         * Key name for the max dynamic shortcuts per app. (int)
196         */
197        String KEY_MAX_SHORTCUTS = "max_shortcuts";
198
199        /**
200         * Key name for icon compression quality, 0-100.
201         */
202        String KEY_ICON_QUALITY = "icon_quality";
203
204        /**
205         * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
206         */
207        String KEY_ICON_FORMAT = "icon_format";
208    }
209
210    final Context mContext;
211
212    private final Object mLock = new Object();
213
214    private final Handler mHandler;
215
216    @GuardedBy("mLock")
217    private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
218
219    @GuardedBy("mLock")
220    private long mRawLastResetTime;
221
222    /**
223     * User ID -> UserShortcuts
224     */
225    @GuardedBy("mLock")
226    private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
227
228    /**
229     * Max number of dynamic shortcuts that each application can have at a time.
230     */
231    private int mMaxDynamicShortcuts;
232
233    /**
234     * Max number of updating API calls that each application can make a day.
235     */
236    int mMaxDailyUpdates;
237
238    /**
239     * Actual throttling-reset interval.  By default it's a day.
240     */
241    private long mResetInterval;
242
243    /**
244     * Icon max width/height in pixels.
245     */
246    private int mMaxIconDimension;
247
248    private CompressFormat mIconPersistFormat;
249    private int mIconPersistQuality;
250
251    private int mSaveDelayMillis;
252
253    private final IPackageManager mIPackageManager;
254    private final PackageManagerInternal mPackageManagerInternal;
255    private final UserManager mUserManager;
256
257    @GuardedBy("mLock")
258    private List<Integer> mDirtyUserIds = new ArrayList<>();
259
260    private static final int PACKAGE_MATCH_FLAGS =
261            PackageManager.MATCH_DIRECT_BOOT_AWARE
262            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
263            | PackageManager.MATCH_UNINSTALLED_PACKAGES;
264
265    public ShortcutService(Context context) {
266        this(context, BackgroundThread.get().getLooper());
267    }
268
269    @VisibleForTesting
270    ShortcutService(Context context, Looper looper) {
271        mContext = Preconditions.checkNotNull(context);
272        LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
273        mHandler = new Handler(looper);
274        mIPackageManager = AppGlobals.getPackageManager();
275        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
276        mUserManager = context.getSystemService(UserManager.class);
277
278        mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
279    }
280
281    /**
282     * System service lifecycle.
283     */
284    public static final class Lifecycle extends SystemService {
285        final ShortcutService mService;
286
287        public Lifecycle(Context context) {
288            super(context);
289            mService = new ShortcutService(context);
290        }
291
292        @Override
293        public void onStart() {
294            publishBinderService(Context.SHORTCUT_SERVICE, mService);
295        }
296
297        @Override
298        public void onBootPhase(int phase) {
299            mService.onBootPhase(phase);
300        }
301
302        @Override
303        public void onCleanupUser(int userHandle) {
304            mService.handleCleanupUser(userHandle);
305        }
306
307        @Override
308        public void onUnlockUser(int userId) {
309            mService.handleUnlockUser(userId);
310        }
311    }
312
313    /** lifecycle event */
314    void onBootPhase(int phase) {
315        if (DEBUG) {
316            Slog.d(TAG, "onBootPhase: " + phase);
317        }
318        switch (phase) {
319            case SystemService.PHASE_LOCK_SETTINGS_READY:
320                initialize();
321                break;
322        }
323    }
324
325    /** lifecycle event */
326    void handleUnlockUser(int userId) {
327        synchronized (mLock) {
328            // Preload
329            getUserShortcutsLocked(userId);
330
331            cleanupGonePackages(userId);
332        }
333    }
334
335    /** lifecycle event */
336    void handleCleanupUser(int userId) {
337        synchronized (mLock) {
338            unloadUserLocked(userId);
339        }
340    }
341
342    private void unloadUserLocked(int userId) {
343        if (DEBUG) {
344            Slog.d(TAG, "unloadUserLocked: user=" + userId);
345        }
346        // Save all dirty information.
347        saveDirtyInfo();
348
349        // Unload
350        mUsers.delete(userId);
351    }
352
353    /** Return the base state file name */
354    private AtomicFile getBaseStateFile() {
355        final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
356        path.mkdirs();
357        return new AtomicFile(path);
358    }
359
360    /**
361     * Init the instance. (load the state file, etc)
362     */
363    private void initialize() {
364        synchronized (mLock) {
365            loadConfigurationLocked();
366            loadBaseStateLocked();
367        }
368    }
369
370    /**
371     * Load the configuration from Settings.
372     */
373    private void loadConfigurationLocked() {
374        updateConfigurationLocked(injectShortcutManagerConstants());
375    }
376
377    /**
378     * Load the configuration from Settings.
379     */
380    @VisibleForTesting
381    boolean updateConfigurationLocked(String config) {
382        boolean result = true;
383
384        final KeyValueListParser parser = new KeyValueListParser(',');
385        try {
386            parser.setString(config);
387        } catch (IllegalArgumentException e) {
388            // Failed to parse the settings string, log this and move on
389            // with defaults.
390            Slog.e(TAG, "Bad shortcut manager settings", e);
391            result = false;
392        }
393
394        mSaveDelayMillis = (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
395                DEFAULT_SAVE_DELAY_MS);
396
397        mResetInterval = parser.getLong(
398                ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
399                * 1000L;
400
401        mMaxDailyUpdates = (int) parser.getLong(
402                ConfigConstants.KEY_MAX_DAILY_UPDATES, DEFAULT_MAX_DAILY_UPDATES);
403
404        mMaxDynamicShortcuts = (int) parser.getLong(
405                ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP);
406
407        final int iconDimensionDp = injectIsLowRamDevice()
408                ? (int) parser.getLong(
409                    ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
410                    DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
411                : (int) parser.getLong(
412                    ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
413                    DEFAULT_MAX_ICON_DIMENSION_DP);
414
415        mMaxIconDimension = injectDipToPixel(iconDimensionDp);
416
417        mIconPersistFormat = CompressFormat.valueOf(
418                parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
419
420        mIconPersistQuality = (int) parser.getLong(
421                ConfigConstants.KEY_ICON_QUALITY,
422                DEFAULT_ICON_PERSIST_QUALITY);
423
424        return result;
425    }
426
427    @VisibleForTesting
428    String injectShortcutManagerConstants() {
429        return android.provider.Settings.Global.getString(
430                mContext.getContentResolver(),
431                android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
432    }
433
434    @VisibleForTesting
435    int injectDipToPixel(int dip) {
436        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
437                mContext.getResources().getDisplayMetrics());
438    }
439
440    // === Persisting ===
441
442    @Nullable
443    static String parseStringAttribute(XmlPullParser parser, String attribute) {
444        return parser.getAttributeValue(null, attribute);
445    }
446
447    static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) {
448        return parseLongAttribute(parser, attribute) == 1;
449    }
450
451    static int parseIntAttribute(XmlPullParser parser, String attribute) {
452        return (int) parseLongAttribute(parser, attribute);
453    }
454
455    static int parseIntAttribute(XmlPullParser parser, String attribute, int def) {
456        return (int) parseLongAttribute(parser, attribute, def);
457    }
458
459    static long parseLongAttribute(XmlPullParser parser, String attribute) {
460        return parseLongAttribute(parser, attribute, 0);
461    }
462
463    static long parseLongAttribute(XmlPullParser parser, String attribute, long def) {
464        final String value = parseStringAttribute(parser, attribute);
465        if (TextUtils.isEmpty(value)) {
466            return def;
467        }
468        try {
469            return Long.parseLong(value);
470        } catch (NumberFormatException e) {
471            Slog.e(TAG, "Error parsing long " + value);
472            return def;
473        }
474    }
475
476    @Nullable
477    static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
478        final String value = parseStringAttribute(parser, attribute);
479        if (TextUtils.isEmpty(value)) {
480            return null;
481        }
482        return ComponentName.unflattenFromString(value);
483    }
484
485    @Nullable
486    static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
487        final String value = parseStringAttribute(parser, attribute);
488        if (TextUtils.isEmpty(value)) {
489            return null;
490        }
491        try {
492            return Intent.parseUri(value, /* flags =*/ 0);
493        } catch (URISyntaxException e) {
494            Slog.e(TAG, "Error parsing intent", e);
495            return null;
496        }
497    }
498
499    static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
500        if (TextUtils.isEmpty(value)) return;
501
502        out.startTag(null, tag);
503        out.attribute(null, ATTR_VALUE, value);
504        out.endTag(null, tag);
505    }
506
507    static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
508        writeTagValue(out, tag, Long.toString(value));
509    }
510
511    static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
512        if (name == null) return;
513        writeTagValue(out, tag, name.flattenToString());
514    }
515
516    static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
517            throws IOException, XmlPullParserException {
518        if (bundle == null) return;
519
520        out.startTag(null, tag);
521        bundle.saveToXml(out);
522        out.endTag(null, tag);
523    }
524
525    static void writeAttr(XmlSerializer out, String name, String value) throws IOException {
526        if (TextUtils.isEmpty(value)) return;
527
528        out.attribute(null, name, value);
529    }
530
531    static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
532        writeAttr(out, name, String.valueOf(value));
533    }
534
535    static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
536        if (value) {
537            writeAttr(out, name, "1");
538        }
539    }
540
541    static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
542        if (comp == null) return;
543        writeAttr(out, name, comp.flattenToString());
544    }
545
546    static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
547        if (intent == null) return;
548
549        writeAttr(out, name, intent.toUri(/* flags =*/ 0));
550    }
551
552    @VisibleForTesting
553    void saveBaseStateLocked() {
554        final AtomicFile file = getBaseStateFile();
555        if (DEBUG) {
556            Slog.d(TAG, "Saving to " + file.getBaseFile());
557        }
558
559        FileOutputStream outs = null;
560        try {
561            outs = file.startWrite();
562
563            // Write to XML
564            XmlSerializer out = new FastXmlSerializer();
565            out.setOutput(outs, StandardCharsets.UTF_8.name());
566            out.startDocument(null, true);
567            out.startTag(null, TAG_ROOT);
568
569            // Body.
570            writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
571
572            // Epilogue.
573            out.endTag(null, TAG_ROOT);
574            out.endDocument();
575
576            // Close.
577            file.finishWrite(outs);
578        } catch (IOException e) {
579            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
580            file.failWrite(outs);
581        }
582    }
583
584    private void loadBaseStateLocked() {
585        mRawLastResetTime = 0;
586
587        final AtomicFile file = getBaseStateFile();
588        if (DEBUG) {
589            Slog.d(TAG, "Loading from " + file.getBaseFile());
590        }
591        try (FileInputStream in = file.openRead()) {
592            XmlPullParser parser = Xml.newPullParser();
593            parser.setInput(in, StandardCharsets.UTF_8.name());
594
595            int type;
596            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
597                if (type != XmlPullParser.START_TAG) {
598                    continue;
599                }
600                final int depth = parser.getDepth();
601                // Check the root tag
602                final String tag = parser.getName();
603                if (depth == 1) {
604                    if (!TAG_ROOT.equals(tag)) {
605                        Slog.e(TAG, "Invalid root tag: " + tag);
606                        return;
607                    }
608                    continue;
609                }
610                // Assume depth == 2
611                switch (tag) {
612                    case TAG_LAST_RESET_TIME:
613                        mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
614                        break;
615                    default:
616                        Slog.e(TAG, "Invalid tag: " + tag);
617                        break;
618                }
619            }
620        } catch (FileNotFoundException e) {
621            // Use the default
622        } catch (IOException|XmlPullParserException e) {
623            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
624
625            mRawLastResetTime = 0;
626        }
627        // Adjust the last reset time.
628        getLastResetTimeLocked();
629    }
630
631    private void saveUserLocked(@UserIdInt int userId) {
632        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
633        if (DEBUG) {
634            Slog.d(TAG, "Saving to " + path);
635        }
636        path.mkdirs();
637        final AtomicFile file = new AtomicFile(path);
638        FileOutputStream os = null;
639        try {
640            os = file.startWrite();
641
642            saveUserInternalLocked(userId, os, /* forBackup= */ false);
643
644            file.finishWrite(os);
645        } catch (XmlPullParserException|IOException e) {
646            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
647            file.failWrite(os);
648        }
649    }
650
651    private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
652            boolean forBackup) throws IOException, XmlPullParserException {
653
654        final BufferedOutputStream bos = new BufferedOutputStream(os);
655
656        // Write to XML
657        XmlSerializer out = new FastXmlSerializer();
658        out.setOutput(bos, StandardCharsets.UTF_8.name());
659        out.startDocument(null, true);
660
661        getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
662
663        out.endDocument();
664
665        bos.flush();
666        os.flush();
667    }
668
669    static IOException throwForInvalidTag(int depth, String tag) throws IOException {
670        throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
671    }
672
673    static void warnForInvalidTag(int depth, String tag) throws IOException {
674        Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
675    }
676
677    @Nullable
678    private ShortcutUser loadUserLocked(@UserIdInt int userId) {
679        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
680        if (DEBUG) {
681            Slog.d(TAG, "Loading from " + path);
682        }
683        final AtomicFile file = new AtomicFile(path);
684
685        final FileInputStream in;
686        try {
687            in = file.openRead();
688        } catch (FileNotFoundException e) {
689            if (DEBUG) {
690                Slog.d(TAG, "Not found " + path);
691            }
692            return null;
693        }
694        try {
695            return loadUserInternal(userId, in, /* forBackup= */ false);
696        } catch (IOException|XmlPullParserException e) {
697            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
698            return null;
699        } finally {
700            IoUtils.closeQuietly(in);
701        }
702    }
703
704    private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
705            boolean fromBackup) throws XmlPullParserException, IOException {
706
707        final BufferedInputStream bis = new BufferedInputStream(is);
708
709        ShortcutUser ret = null;
710        XmlPullParser parser = Xml.newPullParser();
711        parser.setInput(bis, StandardCharsets.UTF_8.name());
712
713        int type;
714        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
715            if (type != XmlPullParser.START_TAG) {
716                continue;
717            }
718            final int depth = parser.getDepth();
719
720            final String tag = parser.getName();
721            if (DEBUG_LOAD) {
722                Slog.d(TAG, String.format("depth=%d type=%d name=%s",
723                        depth, type, tag));
724            }
725            if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
726                ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
727                continue;
728            }
729            throwForInvalidTag(depth, tag);
730        }
731        return ret;
732    }
733
734    private void scheduleSaveBaseState() {
735        scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
736    }
737
738    void scheduleSaveUser(@UserIdInt int userId) {
739        scheduleSaveInner(userId);
740    }
741
742    // In order to re-schedule, we need to reuse the same instance, so keep it in final.
743    private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo;
744
745    private void scheduleSaveInner(@UserIdInt int userId) {
746        if (DEBUG) {
747            Slog.d(TAG, "Scheduling to save for " + userId);
748        }
749        synchronized (mLock) {
750            if (!mDirtyUserIds.contains(userId)) {
751                mDirtyUserIds.add(userId);
752            }
753        }
754        // If already scheduled, remove that and re-schedule in N seconds.
755        mHandler.removeCallbacks(mSaveDirtyInfoRunner);
756        mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
757    }
758
759    @VisibleForTesting
760    void saveDirtyInfo() {
761        if (DEBUG) {
762            Slog.d(TAG, "saveDirtyInfo");
763        }
764        synchronized (mLock) {
765            for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
766                final int userId = mDirtyUserIds.get(i);
767                if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
768                    saveBaseStateLocked();
769                } else {
770                    saveUserLocked(userId);
771                }
772            }
773            mDirtyUserIds.clear();
774        }
775    }
776
777    /** Return the last reset time. */
778    long getLastResetTimeLocked() {
779        updateTimesLocked();
780        return mRawLastResetTime;
781    }
782
783    /** Return the next reset time. */
784    long getNextResetTimeLocked() {
785        updateTimesLocked();
786        return mRawLastResetTime + mResetInterval;
787    }
788
789    static boolean isClockValid(long time) {
790        return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
791    }
792
793    /**
794     * Update the last reset time.
795     */
796    private void updateTimesLocked() {
797
798        final long now = injectCurrentTimeMillis();
799
800        final long prevLastResetTime = mRawLastResetTime;
801
802        if (mRawLastResetTime == 0) { // first launch.
803            // TODO Randomize??
804            mRawLastResetTime = now;
805        } else if (now < mRawLastResetTime) {
806            // Clock rewound.
807            if (isClockValid(now)) {
808                Slog.w(TAG, "Clock rewound");
809                // TODO Randomize??
810                mRawLastResetTime = now;
811            }
812        } else {
813            if ((mRawLastResetTime + mResetInterval) <= now) {
814                final long offset = mRawLastResetTime % mResetInterval;
815                mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
816            }
817        }
818        if (prevLastResetTime != mRawLastResetTime) {
819            scheduleSaveBaseState();
820        }
821    }
822
823    @GuardedBy("mLock")
824    @NonNull
825    boolean isUserLoadedLocked(@UserIdInt int userId) {
826        return mUsers.get(userId) != null;
827    }
828
829    /** Return the per-user state. */
830    @GuardedBy("mLock")
831    @NonNull
832    ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
833        ShortcutUser userPackages = mUsers.get(userId);
834        if (userPackages == null) {
835            userPackages = loadUserLocked(userId);
836            if (userPackages == null) {
837                userPackages = new ShortcutUser(userId);
838            }
839            mUsers.put(userId, userPackages);
840        }
841        return userPackages;
842    }
843
844    /** Return the per-user per-package state. */
845    @GuardedBy("mLock")
846    @NonNull
847    ShortcutPackage getPackageShortcutsLocked(
848            @NonNull String packageName, @UserIdInt int userId) {
849        return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
850    }
851
852    @GuardedBy("mLock")
853    @NonNull
854    ShortcutLauncher getLauncherShortcuts(
855            @NonNull String packageName, @UserIdInt int userId, @UserIdInt int launcherUserId) {
856        return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName, launcherUserId);
857    }
858
859    // === Caller validation ===
860
861    void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
862        if (shortcut.getBitmapPath() != null) {
863            if (DEBUG) {
864                Slog.d(TAG, "Removing " + shortcut.getBitmapPath());
865            }
866            new File(shortcut.getBitmapPath()).delete();
867
868            shortcut.setBitmapPath(null);
869            shortcut.setIconResourceId(0);
870            shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
871        }
872    }
873
874    @VisibleForTesting
875    static class FileOutputStreamWithPath extends FileOutputStream {
876        private final File mFile;
877
878        public FileOutputStreamWithPath(File file) throws FileNotFoundException {
879            super(file);
880            mFile = file;
881        }
882
883        public File getFile() {
884            return mFile;
885        }
886    }
887
888    /**
889     * Build the cached bitmap filename for a shortcut icon.
890     *
891     * The filename will be based on the ID, except certain characters will be escaped.
892     */
893    @VisibleForTesting
894    FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
895            throws IOException {
896        final File packagePath = new File(getUserBitmapFilePath(userId),
897                shortcut.getPackageName());
898        if (!packagePath.isDirectory()) {
899            packagePath.mkdirs();
900            if (!packagePath.isDirectory()) {
901                throw new IOException("Unable to create directory " + packagePath);
902            }
903            SELinux.restorecon(packagePath);
904        }
905
906        final String baseName = String.valueOf(injectCurrentTimeMillis());
907        for (int suffix = 0;; suffix++) {
908            final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
909            final File file = new File(packagePath, filename);
910            if (!file.exists()) {
911                if (DEBUG) {
912                    Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
913                }
914                return new FileOutputStreamWithPath(file);
915            }
916        }
917    }
918
919    void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) {
920        if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
921            return;
922        }
923
924        final long token = injectClearCallingIdentity();
925        try {
926            // Clear icon info on the shortcut.
927            shortcut.setIconResourceId(0);
928            shortcut.setBitmapPath(null);
929
930            final Icon icon = shortcut.getIcon();
931            if (icon == null) {
932                return; // has no icon
933            }
934
935            Bitmap bitmap = null;
936            try {
937                switch (icon.getType()) {
938                    case Icon.TYPE_RESOURCE: {
939                        injectValidateIconResPackage(shortcut, icon);
940
941                        shortcut.setIconResourceId(icon.getResId());
942                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
943                        return;
944                    }
945                    case Icon.TYPE_BITMAP: {
946                        bitmap = icon.getBitmap();
947                        break;
948                    }
949                    case Icon.TYPE_URI: {
950                        final Uri uri = ContentProvider.maybeAddUserId(icon.getUri(), userId);
951
952                        try (InputStream is = mContext.getContentResolver().openInputStream(uri)) {
953
954                            bitmap = BitmapFactory.decodeStream(is);
955
956                        } catch (IOException e) {
957                            Slog.e(TAG, "Unable to load icon from " + uri);
958                            return;
959                        }
960                        break;
961                    }
962                    default:
963                        // This shouldn't happen because we've already validated the icon, but
964                        // just in case.
965                        throw ShortcutInfo.getInvalidIconException();
966                }
967                if (bitmap == null) {
968                    Slog.e(TAG, "Null bitmap detected");
969                    return;
970                }
971                // Shrink and write to the file.
972                File path = null;
973                try {
974                    final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut);
975                    try {
976                        path = out.getFile();
977
978                        shrinkBitmap(bitmap, mMaxIconDimension)
979                                .compress(mIconPersistFormat, mIconPersistQuality, out);
980
981                        shortcut.setBitmapPath(out.getFile().getAbsolutePath());
982                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
983                    } finally {
984                        IoUtils.closeQuietly(out);
985                    }
986                } catch (IOException|RuntimeException e) {
987                    // STOPSHIP Change wtf to e
988                    Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
989                    if (path != null && path.exists()) {
990                        path.delete();
991                    }
992                }
993            } finally {
994                if (bitmap != null) {
995                    bitmap.recycle();
996                }
997                // Once saved, we won't use the original icon information, so null it out.
998                shortcut.clearIcon();
999            }
1000        } finally {
1001            injectRestoreCallingIdentity(token);
1002        }
1003    }
1004
1005    // Unfortunately we can't do this check in unit tests because we fake creator package names,
1006    // so override in unit tests.
1007    // TODO CTS this case.
1008    void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
1009        if (!shortcut.getPackageName().equals(icon.getResPackage())) {
1010            throw new IllegalArgumentException(
1011                    "Icon resource must reside in shortcut owner package");
1012        }
1013    }
1014
1015    @VisibleForTesting
1016    static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
1017        // Original width/height.
1018        final int ow = in.getWidth();
1019        final int oh = in.getHeight();
1020        if ((ow <= maxSize) && (oh <= maxSize)) {
1021            if (DEBUG) {
1022                Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
1023            }
1024            return in;
1025        }
1026        final int longerDimension = Math.max(ow, oh);
1027
1028        // New width and height.
1029        final int nw = ow * maxSize / longerDimension;
1030        final int nh = oh * maxSize / longerDimension;
1031        if (DEBUG) {
1032            Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
1033                    ow, oh, nw, nh));
1034        }
1035
1036        final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
1037        final Canvas c = new Canvas(scaledBitmap);
1038
1039        final RectF dst = new RectF(0, 0, nw, nh);
1040
1041        c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
1042
1043        in.recycle();
1044
1045        return scaledBitmap;
1046    }
1047
1048    // === Caller validation ===
1049
1050    private boolean isCallerSystem() {
1051        final int callingUid = injectBinderCallingUid();
1052         return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
1053    }
1054
1055    private boolean isCallerShell() {
1056        final int callingUid = injectBinderCallingUid();
1057        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
1058    }
1059
1060    private void enforceSystemOrShell() {
1061        Preconditions.checkState(isCallerSystem() || isCallerShell(),
1062                "Caller must be system or shell");
1063    }
1064
1065    private void enforceShell() {
1066        Preconditions.checkState(isCallerShell(), "Caller must be shell");
1067    }
1068
1069    private void enforceSystem() {
1070        Preconditions.checkState(isCallerSystem(), "Caller must be system");
1071    }
1072
1073    private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
1074        Preconditions.checkStringNotEmpty(packageName, "packageName");
1075
1076        if (isCallerSystem()) {
1077            return; // no check
1078        }
1079
1080        final int callingUid = injectBinderCallingUid();
1081
1082        // Otherwise, make sure the arguments are valid.
1083        if (UserHandle.getUserId(callingUid) != userId) {
1084            throw new SecurityException("Invalid user-ID");
1085        }
1086        if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) {
1087            return; // Caller is valid.
1088        }
1089        throw new SecurityException("Caller UID= doesn't own " + packageName);
1090    }
1091
1092    void postToHandler(Runnable r) {
1093        mHandler.post(r);
1094    }
1095
1096    /**
1097     * Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
1098     */
1099    void enforceMaxDynamicShortcuts(int numShortcuts) {
1100        if (numShortcuts > mMaxDynamicShortcuts) {
1101            throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
1102        }
1103    }
1104
1105    /**
1106     * - Sends a notification to LauncherApps
1107     * - Write to file
1108     */
1109    private void userPackageChanged(@NonNull String packageName, @UserIdInt int userId) {
1110        notifyListeners(packageName, userId);
1111        scheduleSaveUser(userId);
1112    }
1113
1114    private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
1115        if (!mUserManager.isUserRunning(userId)) {
1116            return;
1117        }
1118        postToHandler(() -> {
1119            final ArrayList<ShortcutChangeListener> copy;
1120            synchronized (mLock) {
1121                copy = new ArrayList<>(mListeners);
1122            }
1123            // Note onShortcutChanged() needs to be called with the system service permissions.
1124            for (int i = copy.size() - 1; i >= 0; i--) {
1125                copy.get(i).onShortcutChanged(packageName, userId);
1126            }
1127        });
1128    }
1129
1130    /**
1131     * Clean up / validate an incoming shortcut.
1132     * - Make sure all mandatory fields are set.
1133     * - Make sure the intent's extras are persistable, and them to set
1134     *  {@link ShortcutInfo#mIntentPersistableExtras}.  Also clear its extras.
1135     * - Clear flags.
1136     *
1137     * TODO Detailed unit tests
1138     */
1139    private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
1140        Preconditions.checkNotNull(shortcut, "Null shortcut detected");
1141        if (shortcut.getActivityComponent() != null) {
1142            Preconditions.checkState(
1143                    shortcut.getPackageName().equals(
1144                            shortcut.getActivityComponent().getPackageName()),
1145                    "Activity package name mismatch");
1146        }
1147
1148        if (!forUpdate) {
1149            shortcut.enforceMandatoryFields();
1150        }
1151        if (shortcut.getIcon() != null) {
1152            ShortcutInfo.validateIcon(shortcut.getIcon());
1153        }
1154
1155        validateForXml(shortcut.getId());
1156        validateForXml(shortcut.getTitle());
1157        validatePersistableBundleForXml(shortcut.getIntentPersistableExtras());
1158        validatePersistableBundleForXml(shortcut.getExtras());
1159
1160        shortcut.replaceFlags(0);
1161    }
1162
1163    // KXmlSerializer is strict and doesn't allow certain characters, so we disallow those
1164    // characters.
1165
1166    private static void validatePersistableBundleForXml(PersistableBundle b) {
1167        if (b == null || b.size() == 0) {
1168            return;
1169        }
1170        for (String key : b.keySet()) {
1171            validateForXml(key);
1172            final Object value = b.get(key);
1173            if (value == null) {
1174                continue;
1175            } else if (value instanceof String) {
1176                validateForXml((String) value);
1177            } else if (value instanceof String[]) {
1178                for (String v : (String[]) value) {
1179                    validateForXml(v);
1180                }
1181            } else if (value instanceof PersistableBundle) {
1182                validatePersistableBundleForXml((PersistableBundle) value);
1183            }
1184        }
1185    }
1186
1187    private static void validateForXml(String s) {
1188        if (TextUtils.isEmpty(s)) {
1189            return;
1190        }
1191        for (int i = s.length() - 1; i >= 0; i--) {
1192            if (!isAllowedInXml(s.charAt(i))) {
1193                throw new IllegalArgumentException("Unsupported character detected in: " + s);
1194            }
1195        }
1196    }
1197
1198    private static boolean isAllowedInXml(char c) {
1199        return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
1200    }
1201
1202    // === APIs ===
1203
1204    @Override
1205    public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1206            @UserIdInt int userId) {
1207        verifyCaller(packageName, userId);
1208
1209        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1210        final int size = newShortcuts.size();
1211
1212        synchronized (mLock) {
1213            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1214
1215            ps.ensureNotShadowAndSave(this);
1216
1217            // Throttling.
1218            if (!ps.tryApiCall(this)) {
1219                return false;
1220            }
1221            enforceMaxDynamicShortcuts(size);
1222
1223            // Validate the shortcuts.
1224            for (int i = 0; i < size; i++) {
1225                fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
1226            }
1227
1228            // First, remove all un-pinned; dynamic shortcuts
1229            ps.deleteAllDynamicShortcuts(this);
1230
1231            // Then, add/update all.  We need to make sure to take over "pinned" flag.
1232            for (int i = 0; i < size; i++) {
1233                final ShortcutInfo newShortcut = newShortcuts.get(i);
1234                ps.addDynamicShortcut(this, newShortcut);
1235            }
1236        }
1237        userPackageChanged(packageName, userId);
1238        return true;
1239    }
1240
1241    @Override
1242    public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1243            @UserIdInt int userId) {
1244        verifyCaller(packageName, userId);
1245
1246        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1247        final int size = newShortcuts.size();
1248
1249        synchronized (mLock) {
1250            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1251
1252            ps.ensureNotShadowAndSave(this);
1253
1254            // Throttling.
1255            if (!ps.tryApiCall(this)) {
1256                return false;
1257            }
1258
1259            for (int i = 0; i < size; i++) {
1260                final ShortcutInfo source = newShortcuts.get(i);
1261                fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
1262
1263                final ShortcutInfo target = ps.findShortcutById(source.getId());
1264                if (target != null) {
1265                    final boolean replacingIcon = (source.getIcon() != null);
1266                    if (replacingIcon) {
1267                        removeIcon(userId, target);
1268                    }
1269
1270                    target.copyNonNullFieldsFrom(source);
1271
1272                    if (replacingIcon) {
1273                        saveIconAndFixUpShortcut(userId, target);
1274                    }
1275                }
1276            }
1277        }
1278        userPackageChanged(packageName, userId);
1279
1280        return true;
1281    }
1282
1283    @Override
1284    public boolean addDynamicShortcut(String packageName, ShortcutInfo newShortcut,
1285            @UserIdInt int userId) {
1286        verifyCaller(packageName, userId);
1287
1288        synchronized (mLock) {
1289            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1290
1291            ps.ensureNotShadowAndSave(this);
1292
1293            // Throttling.
1294            if (!ps.tryApiCall(this)) {
1295                return false;
1296            }
1297
1298            // Validate the shortcut.
1299            fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
1300
1301            // Add it.
1302            ps.addDynamicShortcut(this, newShortcut);
1303        }
1304        userPackageChanged(packageName, userId);
1305
1306        return true;
1307    }
1308
1309    @Override
1310    public void deleteDynamicShortcut(String packageName, String shortcutId,
1311            @UserIdInt int userId) {
1312        verifyCaller(packageName, userId);
1313        Preconditions.checkStringNotEmpty(shortcutId, "shortcutId must be provided");
1314
1315        synchronized (mLock) {
1316            getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this, shortcutId);
1317        }
1318        userPackageChanged(packageName, userId);
1319    }
1320
1321    @Override
1322    public void deleteAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
1323        verifyCaller(packageName, userId);
1324
1325        synchronized (mLock) {
1326            getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
1327        }
1328        userPackageChanged(packageName, userId);
1329    }
1330
1331    @Override
1332    public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
1333            @UserIdInt int userId) {
1334        verifyCaller(packageName, userId);
1335        synchronized (mLock) {
1336            return getShortcutsWithQueryLocked(
1337                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1338                    ShortcutInfo::isDynamic);
1339        }
1340    }
1341
1342    @Override
1343    public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
1344            @UserIdInt int userId) {
1345        verifyCaller(packageName, userId);
1346        synchronized (mLock) {
1347            return getShortcutsWithQueryLocked(
1348                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1349                    ShortcutInfo::isPinned);
1350        }
1351    }
1352
1353    private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
1354            @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
1355
1356        final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1357
1358        getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags);
1359
1360        return new ParceledListSlice<>(ret);
1361    }
1362
1363    @Override
1364    public int getMaxDynamicShortcutCount(String packageName, @UserIdInt int userId)
1365            throws RemoteException {
1366        verifyCaller(packageName, userId);
1367
1368        return mMaxDynamicShortcuts;
1369    }
1370
1371    @Override
1372    public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
1373        verifyCaller(packageName, userId);
1374
1375        synchronized (mLock) {
1376            return mMaxDailyUpdates
1377                    - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
1378        }
1379    }
1380
1381    @Override
1382    public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
1383        verifyCaller(packageName, userId);
1384
1385        synchronized (mLock) {
1386            return getNextResetTimeLocked();
1387        }
1388    }
1389
1390    @Override
1391    public int getIconMaxDimensions(String packageName, int userId) throws RemoteException {
1392        synchronized (mLock) {
1393            return mMaxIconDimension;
1394        }
1395    }
1396
1397    /**
1398     * Reset all throttling, for developer options and command line.  Only system/shell can call it.
1399     */
1400    @Override
1401    public void resetThrottling() {
1402        enforceSystemOrShell();
1403
1404        resetThrottlingInner(getCallingUserId());
1405    }
1406
1407    void resetThrottlingInner(@UserIdInt int userId) {
1408        synchronized (mLock) {
1409            getUserShortcutsLocked(userId).resetThrottling();
1410        }
1411        scheduleSaveUser(userId);
1412        Slog.i(TAG, "ShortcutManager: throttling counter reset");
1413    }
1414
1415    // We override this method in unit tests to do a simpler check.
1416    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
1417        return hasShortcutHostPermissionInner(callingPackage, userId);
1418    }
1419
1420    // This method is extracted so we can directly call this method from unit tests,
1421    // even when hasShortcutPermission() is overridden.
1422    @VisibleForTesting
1423    boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
1424        synchronized (mLock) {
1425            long start = System.currentTimeMillis();
1426
1427            final ShortcutUser user = getUserShortcutsLocked(userId);
1428
1429            final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
1430
1431            // Default launcher from package manager.
1432            final ComponentName defaultLauncher = injectPackageManagerInternal()
1433                    .getHomeActivitiesAsUser(allHomeCandidates, userId);
1434
1435            ComponentName detected;
1436            if (defaultLauncher != null) {
1437                detected = defaultLauncher;
1438                if (DEBUG) {
1439                    Slog.v(TAG, "Default launcher from PM: " + detected);
1440                }
1441            } else {
1442                detected = user.getLauncherComponent();
1443
1444                // TODO: Make sure it's still enabled.
1445                if (DEBUG) {
1446                    Slog.v(TAG, "Cached launcher: " + detected);
1447                }
1448            }
1449
1450            if (detected == null) {
1451                // If we reach here, that means it's the first check since the user was created,
1452                // and there's already multiple launchers and there's no default set.
1453                // Find the system one with the highest priority.
1454                // (We need to check the priority too because of FallbackHome in Settings.)
1455                // If there's no system launcher yet, then no one can access shortcuts, until
1456                // the user explicitly
1457                final int size = allHomeCandidates.size();
1458
1459                int lastPriority = Integer.MIN_VALUE;
1460                for (int i = 0; i < size; i++) {
1461                    final ResolveInfo ri = allHomeCandidates.get(i);
1462                    if (!ri.activityInfo.applicationInfo.isSystemApp()) {
1463                        continue;
1464                    }
1465                    if (DEBUG) {
1466                        Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
1467                                ri.activityInfo.getComponentName(), ri.priority));
1468                    }
1469                    if (ri.priority < lastPriority) {
1470                        continue;
1471                    }
1472                    detected = ri.activityInfo.getComponentName();
1473                    lastPriority = ri.priority;
1474                }
1475            }
1476            final long end = System.currentTimeMillis();
1477            if (DEBUG) {
1478                Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start));
1479            }
1480            if (detected != null) {
1481                if (DEBUG) {
1482                    Slog.v(TAG, "Detected launcher: " + detected);
1483                }
1484                user.setLauncherComponent(this, detected);
1485                return detected.getPackageName().equals(callingPackage);
1486            } else {
1487                // Default launcher not found.
1488                return false;
1489            }
1490        }
1491    }
1492
1493    // === House keeping ===
1494
1495    @VisibleForTesting
1496    void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
1497
1498        // TODO Don't remove shadow packages' information.
1499
1500        final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
1501
1502        final ShortcutUser mUser = getUserShortcutsLocked(owningUserId);
1503        boolean doNotify = false;
1504
1505        // First, remove the package from the package list (if the package is a publisher).
1506        if (packageUserId == owningUserId) {
1507            if (mUser.getPackages().remove(packageName) != null) {
1508                doNotify = true;
1509            }
1510        }
1511
1512        // Also remove from the launcher list (if the package is a launcher).
1513        mUser.removeLauncher(packageUserId, packageName);
1514
1515        // Then remove pinned shortcuts from all launchers.
1516        final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = mUser.getAllLaunchers();
1517        for (int i = launchers.size() - 1; i >= 0; i--) {
1518            launchers.valueAt(i).cleanUpPackage(packageName);
1519        }
1520        // Now there may be orphan shortcuts because we removed pinned shortucts at the previous
1521        // step.  Remove them too.
1522        for (int i = mUser.getPackages().size() - 1; i >= 0; i--) {
1523            mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
1524        }
1525
1526        scheduleSaveUser(owningUserId);
1527
1528        if (doNotify) {
1529            notifyListeners(packageName, owningUserId);
1530        }
1531
1532        if (!wasUserLoaded) {
1533            // Note this will execute the scheduled save.
1534            unloadUserLocked(owningUserId);
1535        }
1536    }
1537
1538    /**
1539     * Entry point from {@link LauncherApps}.
1540     */
1541    private class LocalService extends ShortcutServiceInternal {
1542        @Override
1543        public List<ShortcutInfo> getShortcuts(int launcherUserId,
1544                @NonNull String callingPackage, long changedSince,
1545                @Nullable String packageName, @Nullable ComponentName componentName,
1546                int queryFlags, int userId) {
1547            final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1548            final int cloneFlag =
1549                    ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) == 0)
1550                            ? ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER
1551                            : ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
1552
1553            synchronized (mLock) {
1554                if (packageName != null) {
1555                    getShortcutsInnerLocked(launcherUserId,
1556                            callingPackage, packageName, changedSince,
1557                            componentName, queryFlags, userId, ret, cloneFlag);
1558                } else {
1559                    final ArrayMap<String, ShortcutPackage> packages =
1560                            getUserShortcutsLocked(userId).getPackages();
1561                    for (int i = packages.size() - 1; i >= 0; i--) {
1562                        getShortcutsInnerLocked(launcherUserId,
1563                                callingPackage, packages.keyAt(i), changedSince,
1564                                componentName, queryFlags, userId, ret, cloneFlag);
1565                    }
1566                }
1567            }
1568            return ret;
1569        }
1570
1571        private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
1572                @Nullable String packageName,long changedSince,
1573                @Nullable ComponentName componentName, int queryFlags,
1574                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
1575            getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
1576                    (ShortcutInfo si) -> {
1577                        if (si.getLastChangedTimestamp() < changedSince) {
1578                            return false;
1579                        }
1580                        if (componentName != null
1581                                && !componentName.equals(si.getActivityComponent())) {
1582                            return false;
1583                        }
1584                        final boolean matchDynamic =
1585                                ((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
1586                                        && si.isDynamic();
1587                        final boolean matchPinned =
1588                                ((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
1589                                        && si.isPinned();
1590                        return matchDynamic || matchPinned;
1591                    }, cloneFlag, callingPackage, launcherUserId);
1592        }
1593
1594        @Override
1595        public List<ShortcutInfo> getShortcutInfo(int launcherUserId,
1596                @NonNull String callingPackage,
1597                @NonNull String packageName, @Nullable List<String> ids, int userId) {
1598            // Calling permission must be checked by LauncherAppsImpl.
1599            Preconditions.checkStringNotEmpty(packageName, "packageName");
1600
1601            final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
1602            final ArraySet<String> idSet = new ArraySet<>(ids);
1603            synchronized (mLock) {
1604                getPackageShortcutsLocked(packageName, userId).findAll(
1605                        ShortcutService.this, ret,
1606                        (ShortcutInfo si) -> idSet.contains(si.getId()),
1607                        ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER, callingPackage, launcherUserId);
1608            }
1609            return ret;
1610        }
1611
1612        @Override
1613        public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
1614                @NonNull String packageName, @NonNull String shortcutId, int userId) {
1615            Preconditions.checkStringNotEmpty(packageName, "packageName");
1616            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
1617
1618            synchronized (mLock) {
1619                final ShortcutInfo si = getShortcutInfoLocked(
1620                        launcherUserId, callingPackage, packageName, shortcutId, userId);
1621                return si != null && si.isPinned();
1622            }
1623        }
1624
1625        public ShortcutInfo getShortcutInfoLocked(
1626                int launcherUserId, @NonNull String callingPackage,
1627                @NonNull String packageName, @NonNull String shortcutId, int userId) {
1628            Preconditions.checkStringNotEmpty(packageName, "packageName");
1629            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
1630
1631            final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
1632            getPackageShortcutsLocked(packageName, userId).findAll(
1633                    ShortcutService.this, list,
1634                    (ShortcutInfo si) -> shortcutId.equals(si.getId()),
1635                    /* clone flags=*/ 0, callingPackage, launcherUserId);
1636            return list.size() == 0 ? null : list.get(0);
1637        }
1638
1639        @Override
1640        public void pinShortcuts(int launcherUserId,
1641                @NonNull String callingPackage, @NonNull String packageName,
1642                @NonNull List<String> shortcutIds, int userId) {
1643            // Calling permission must be checked by LauncherAppsImpl.
1644            Preconditions.checkStringNotEmpty(packageName, "packageName");
1645            Preconditions.checkNotNull(shortcutIds, "shortcutIds");
1646
1647            synchronized (mLock) {
1648                final ShortcutLauncher launcher =
1649                        getLauncherShortcuts(callingPackage, userId, launcherUserId);
1650
1651                launcher.ensureNotShadowAndSave(ShortcutService.this);
1652
1653                launcher.pinShortcuts(
1654                        ShortcutService.this, userId, packageName, shortcutIds);
1655            }
1656            userPackageChanged(packageName, userId);
1657        }
1658
1659        @Override
1660        public Intent createShortcutIntent(int launcherUserId,
1661                @NonNull String callingPackage,
1662                @NonNull String packageName, @NonNull String shortcutId, int userId) {
1663            // Calling permission must be checked by LauncherAppsImpl.
1664            Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
1665            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
1666
1667            synchronized (mLock) {
1668                // Make sure the shortcut is actually visible to the launcher.
1669                final ShortcutInfo si = getShortcutInfoLocked(
1670                        launcherUserId, callingPackage, packageName, shortcutId, userId);
1671                // "si == null" should suffice here, but check the flags too just to make sure.
1672                if (si == null || !(si.isDynamic() || si.isPinned())) {
1673                    return null;
1674                }
1675                return si.getIntent();
1676            }
1677        }
1678
1679        @Override
1680        public void addListener(@NonNull ShortcutChangeListener listener) {
1681            synchronized (mLock) {
1682                mListeners.add(Preconditions.checkNotNull(listener));
1683            }
1684        }
1685
1686        @Override
1687        public int getShortcutIconResId(int launcherUserId,
1688                @NonNull String callingPackage,
1689                @NonNull ShortcutInfo shortcut, int userId) {
1690            Preconditions.checkNotNull(shortcut, "shortcut");
1691
1692            synchronized (mLock) {
1693                final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1694                        shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
1695                return (shortcutInfo != null && shortcutInfo.hasIconResource())
1696                        ? shortcutInfo.getIconResourceId() : 0;
1697            }
1698        }
1699
1700        @Override
1701        public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
1702                @NonNull String callingPackage,
1703                @NonNull ShortcutInfo shortcutIn, int userId) {
1704            Preconditions.checkNotNull(shortcutIn, "shortcut");
1705
1706            synchronized (mLock) {
1707                final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
1708                        shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
1709                if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
1710                    return null;
1711                }
1712                try {
1713                    if (shortcutInfo.getBitmapPath() == null) {
1714                        Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
1715                        return null;
1716                    }
1717                    return ParcelFileDescriptor.open(
1718                            new File(shortcutInfo.getBitmapPath()),
1719                            ParcelFileDescriptor.MODE_READ_ONLY);
1720                } catch (FileNotFoundException e) {
1721                    Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath());
1722                    return null;
1723                }
1724            }
1725        }
1726
1727        @Override
1728        public boolean hasShortcutHostPermission(int launcherUserId,
1729                @NonNull String callingPackage) {
1730            return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
1731        }
1732    }
1733
1734    /**
1735     * Package event callbacks.
1736     */
1737    @VisibleForTesting
1738    final PackageMonitor mPackageMonitor = new PackageMonitor() {
1739        @Override
1740        public void onPackageAdded(String packageName, int uid) {
1741            handlePackageAdded(packageName, getChangingUserId());
1742        }
1743
1744        @Override
1745        public void onPackageUpdateFinished(String packageName, int uid) {
1746            handlePackageUpdateFinished(packageName, getChangingUserId());
1747        }
1748
1749        @Override
1750        public void onPackageRemoved(String packageName, int uid) {
1751            handlePackageRemoved(packageName, getChangingUserId());
1752        }
1753    };
1754
1755    /**
1756     * Called when a user is unlocked.  Check all known packages still exist, and otherwise
1757     * perform cleanup.
1758     */
1759    @VisibleForTesting
1760    void cleanupGonePackages(@UserIdInt int userId) {
1761        if (DEBUG) {
1762            Slog.d(TAG, "cleanupGonePackages() userId=" + userId);
1763        }
1764        final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
1765
1766        synchronized (mLock) {
1767            final ShortcutUser user = getUserShortcutsLocked(userId);
1768
1769            user.forAllPackageItems(spi -> {
1770                if (spi.getPackageInfo().isShadow()) {
1771                    return; // Don't delete shadow information.
1772                }
1773                if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
1774                    return;
1775                }
1776                gonePackages.add(PackageWithUser.of(spi));
1777            });
1778            if (gonePackages.size() > 0) {
1779                for (int i = gonePackages.size() - 1; i >= 0; i--) {
1780                    final PackageWithUser pu = gonePackages.get(i);
1781                    cleanUpPackageLocked(pu.packageName, userId, pu.userId);
1782                }
1783            }
1784        }
1785    }
1786
1787    private void handlePackageAdded(String packageName, @UserIdInt int userId) {
1788        if (DEBUG) {
1789            Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
1790        }
1791        synchronized (mLock) {
1792            getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
1793        }
1794    }
1795
1796    private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
1797        if (DEBUG) {
1798            Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
1799                    packageName, userId));
1800        }
1801
1802        synchronized (mLock) {
1803            getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
1804        }
1805    }
1806
1807    private void handlePackageRemoved(String packageName, @UserIdInt int userId) {
1808        if (DEBUG) {
1809            Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId));
1810        }
1811        synchronized (mLock) {
1812            cleanUpPackageLocked(packageName, userId, userId);
1813        }
1814    }
1815
1816    // === PackageManager interaction ===
1817
1818    PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
1819        return injectPackageInfo(packageName, userId, true);
1820    }
1821
1822    int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
1823        final long token = injectClearCallingIdentity();
1824        try {
1825            return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS
1826                    , userId);
1827        } catch (RemoteException e) {
1828            // Shouldn't happen.
1829            Slog.wtf(TAG, "RemoteException", e);
1830            return -1;
1831        } finally {
1832            injectRestoreCallingIdentity(token);
1833        }
1834    }
1835
1836    @VisibleForTesting
1837    PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
1838            boolean getSignatures) {
1839        final long token = injectClearCallingIdentity();
1840        try {
1841            return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
1842                    | (getSignatures ? PackageManager.GET_SIGNATURES : 0)
1843                    , userId);
1844        } catch (RemoteException e) {
1845            // Shouldn't happen.
1846            Slog.wtf(TAG, "RemoteException", e);
1847            return null;
1848        } finally {
1849            injectRestoreCallingIdentity(token);
1850        }
1851    }
1852
1853    @VisibleForTesting
1854    ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
1855        final long token = injectClearCallingIdentity();
1856        try {
1857            return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
1858        } catch (RemoteException e) {
1859            // Shouldn't happen.
1860            Slog.wtf(TAG, "RemoteException", e);
1861            return null;
1862        } finally {
1863            injectRestoreCallingIdentity(token);
1864        }
1865    }
1866
1867    private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
1868        final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
1869        return (ai != null) && ((ai.flags & flags) == flags);
1870    }
1871
1872    private boolean isPackageInstalled(String packageName, int userId) {
1873        return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
1874    }
1875
1876    // === Backup & restore ===
1877
1878    boolean shouldBackupApp(String packageName, int userId) {
1879        return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
1880    }
1881
1882    @Override
1883    public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException {
1884        enforceSystem();
1885        if (DEBUG) {
1886            Slog.d(TAG, "Backing up user " + userId);
1887        }
1888        synchronized (mLock) {
1889            final ShortcutUser user = getUserShortcutsLocked(userId);
1890            if (user == null) {
1891                Slog.w(TAG, "Can't backup: user not found: id=" + userId);
1892                return null;
1893            }
1894
1895            user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
1896
1897            // Then save.
1898            final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
1899            try {
1900                saveUserInternalLocked(userId, os, /* forBackup */ true);
1901            } catch (XmlPullParserException|IOException e) {
1902                // Shouldn't happen.
1903                Slog.w(TAG, "Backup failed.", e);
1904                return null;
1905            }
1906            return os.toByteArray();
1907        }
1908    }
1909
1910    @Override
1911    public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException {
1912        enforceSystem();
1913        if (DEBUG) {
1914            Slog.d(TAG, "Restoring user " + userId);
1915        }
1916        final ShortcutUser user;
1917        final ByteArrayInputStream is = new ByteArrayInputStream(payload);
1918        try {
1919            user = loadUserInternal(userId, is, /* fromBackup */ true);
1920        } catch (XmlPullParserException|IOException e) {
1921            Slog.w(TAG, "Restoration failed.", e);
1922            return;
1923        }
1924        synchronized (mLock) {
1925            mUsers.put(userId, user);
1926        }
1927    }
1928
1929    // === Dump ===
1930
1931    @Override
1932    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1933        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1934                != PackageManager.PERMISSION_GRANTED) {
1935            pw.println("Permission Denial: can't dump UserManager from from pid="
1936                    + Binder.getCallingPid()
1937                    + ", uid=" + Binder.getCallingUid()
1938                    + " without permission "
1939                    + android.Manifest.permission.DUMP);
1940            return;
1941        }
1942        dumpInner(pw);
1943    }
1944
1945    @VisibleForTesting
1946    void dumpInner(PrintWriter pw) {
1947        synchronized (mLock) {
1948            final long now = injectCurrentTimeMillis();
1949            pw.print("Now: [");
1950            pw.print(now);
1951            pw.print("] ");
1952            pw.print(formatTime(now));
1953
1954            pw.print("  Raw last reset: [");
1955            pw.print(mRawLastResetTime);
1956            pw.print("] ");
1957            pw.print(formatTime(mRawLastResetTime));
1958
1959            final long last = getLastResetTimeLocked();
1960            pw.print("  Last reset: [");
1961            pw.print(last);
1962            pw.print("] ");
1963            pw.print(formatTime(last));
1964
1965            final long next = getNextResetTimeLocked();
1966            pw.print("  Next reset: [");
1967            pw.print(next);
1968            pw.print("] ");
1969            pw.print(formatTime(next));
1970            pw.println();
1971
1972            pw.print("  Max icon dim: ");
1973            pw.print(mMaxIconDimension);
1974            pw.print("  Icon format: ");
1975            pw.print(mIconPersistFormat);
1976            pw.print("  Icon quality: ");
1977            pw.print(mIconPersistQuality);
1978            pw.println();
1979
1980
1981            for (int i = 0; i < mUsers.size(); i++) {
1982                pw.println();
1983                mUsers.valueAt(i).dump(this, pw, "  ");
1984            }
1985        }
1986    }
1987
1988    static String formatTime(long time) {
1989        Time tobj = new Time();
1990        tobj.set(time);
1991        return tobj.format("%Y-%m-%d %H:%M:%S");
1992    }
1993
1994    // === Shell support ===
1995
1996    @Override
1997    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1998            String[] args, ResultReceiver resultReceiver) throws RemoteException {
1999
2000        enforceShell();
2001
2002        (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
2003    }
2004
2005    static class CommandException extends Exception {
2006        public CommandException(String message) {
2007            super(message);
2008        }
2009    }
2010
2011    /**
2012     * Handle "adb shell cmd".
2013     */
2014    private class MyShellCommand extends ShellCommand {
2015
2016        private int mUserId = UserHandle.USER_SYSTEM;
2017
2018        private void parseOptions(boolean takeUser)
2019                throws CommandException {
2020            String opt;
2021            while ((opt = getNextOption()) != null) {
2022                switch (opt) {
2023                    case "--user":
2024                        if (takeUser) {
2025                            mUserId = UserHandle.parseUserArg(getNextArgRequired());
2026                            break;
2027                        }
2028                        // fallthrough
2029                    default:
2030                        throw new CommandException("Unknown option: " + opt);
2031                }
2032            }
2033        }
2034
2035        @Override
2036        public int onCommand(String cmd) {
2037            if (cmd == null) {
2038                return handleDefaultCommands(cmd);
2039            }
2040            final PrintWriter pw = getOutPrintWriter();
2041            try {
2042                switch (cmd) {
2043                    case "reset-package-throttling":
2044                        handleResetPackageThrottling();
2045                        break;
2046                    case "reset-throttling":
2047                        handleResetThrottling();
2048                        break;
2049                    case "override-config":
2050                        handleOverrideConfig();
2051                        break;
2052                    case "reset-config":
2053                        handleResetConfig();
2054                        break;
2055                    case "clear-default-launcher":
2056                        handleClearDefaultLauncher();
2057                        break;
2058                    case "get-default-launcher":
2059                        handleGetDefaultLauncher();
2060                        break;
2061                    case "refresh-default-launcher":
2062                        handleRefreshDefaultLauncher();
2063                        break;
2064                    default:
2065                        return handleDefaultCommands(cmd);
2066                }
2067            } catch (CommandException e) {
2068                pw.println("Error: " + e.getMessage());
2069                return 1;
2070            }
2071            pw.println("Success");
2072            return 0;
2073        }
2074
2075        @Override
2076        public void onHelp() {
2077            final PrintWriter pw = getOutPrintWriter();
2078            pw.println("Usage: cmd shortcut COMMAND [options ...]");
2079            pw.println();
2080            pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
2081            pw.println("    Reset throttling for a package");
2082            pw.println();
2083            pw.println("cmd shortcut reset-throttling");
2084            pw.println("    Reset throttling for all packages and users");
2085            pw.println();
2086            pw.println("cmd shortcut override-config CONFIG");
2087            pw.println("    Override the configuration for testing (will last until reboot)");
2088            pw.println();
2089            pw.println("cmd shortcut reset-config");
2090            pw.println("    Reset the configuration set with \"update-config\"");
2091            pw.println();
2092            pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
2093            pw.println("    Clear the cached default launcher");
2094            pw.println();
2095            pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
2096            pw.println("    Show the cached default launcher");
2097            pw.println();
2098            pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
2099            pw.println("    Refresh the cached default launcher");
2100            pw.println();
2101        }
2102
2103        private int handleResetThrottling() throws CommandException {
2104            parseOptions(/* takeUser =*/ true);
2105
2106            resetThrottlingInner(mUserId);
2107            return 0;
2108        }
2109
2110        private void handleResetPackageThrottling() throws CommandException {
2111            parseOptions(/* takeUser =*/ true);
2112
2113            final String packageName = getNextArgRequired();
2114
2115            synchronized (mLock) {
2116                getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine();
2117                saveUserLocked(mUserId);
2118            }
2119        }
2120
2121        private void handleOverrideConfig() throws CommandException {
2122            final String config = getNextArgRequired();
2123
2124            synchronized (mLock) {
2125                if (!updateConfigurationLocked(config)) {
2126                    throw new CommandException("override-config failed.  See logcat for details.");
2127                }
2128            }
2129        }
2130
2131        private void handleResetConfig() {
2132            synchronized (mLock) {
2133                loadConfigurationLocked();
2134            }
2135        }
2136
2137        private void clearLauncher() {
2138            synchronized (mLock) {
2139                getUserShortcutsLocked(mUserId).setLauncherComponent(
2140                        ShortcutService.this, null);
2141            }
2142        }
2143
2144        private void showLauncher() {
2145            synchronized (mLock) {
2146                // This ensures to set the cached launcher.  Package name doesn't matter.
2147                hasShortcutHostPermissionInner("-", mUserId);
2148
2149                getOutPrintWriter().println("Launcher: "
2150                        + getUserShortcutsLocked(mUserId).getLauncherComponent());
2151            }
2152        }
2153
2154        private void handleClearDefaultLauncher() throws CommandException {
2155            parseOptions(/* takeUser =*/ true);
2156
2157            clearLauncher();
2158        }
2159
2160        private void handleGetDefaultLauncher() throws CommandException {
2161            parseOptions(/* takeUser =*/ true);
2162
2163            showLauncher();
2164        }
2165
2166        private void handleRefreshDefaultLauncher() throws CommandException {
2167            parseOptions(/* takeUser =*/ true);
2168
2169            clearLauncher();
2170            showLauncher();
2171        }
2172    }
2173
2174    // === Unit test support ===
2175
2176    // Injection point.
2177    @VisibleForTesting
2178    long injectCurrentTimeMillis() {
2179        return System.currentTimeMillis();
2180    }
2181
2182    // Injection point.
2183    @VisibleForTesting
2184    int injectBinderCallingUid() {
2185        return getCallingUid();
2186    }
2187
2188    private int getCallingUserId() {
2189        return UserHandle.getUserId(injectBinderCallingUid());
2190    }
2191
2192    // Injection point.
2193    @VisibleForTesting
2194    long injectClearCallingIdentity() {
2195        return Binder.clearCallingIdentity();
2196    }
2197
2198    // Injection point.
2199    @VisibleForTesting
2200    void injectRestoreCallingIdentity(long token) {
2201        Binder.restoreCallingIdentity(token);
2202    }
2203
2204    final void wtf(String message) {
2205        Slog.wtf(TAG, message, /* exception= */ null);
2206    }
2207
2208    void wtf(String message, Exception e) {
2209        Slog.wtf(TAG, message, e);
2210    }
2211
2212    @VisibleForTesting
2213    File injectSystemDataPath() {
2214        return Environment.getDataSystemDirectory();
2215    }
2216
2217    @VisibleForTesting
2218    File injectUserDataPath(@UserIdInt int userId) {
2219        return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
2220    }
2221
2222    @VisibleForTesting
2223    boolean injectIsLowRamDevice() {
2224        return ActivityManager.isLowRamDeviceStatic();
2225    }
2226
2227    @VisibleForTesting
2228    PackageManagerInternal injectPackageManagerInternal() {
2229        return mPackageManagerInternal;
2230    }
2231
2232    @VisibleForTesting
2233    File getUserBitmapFilePath(@UserIdInt int userId) {
2234        return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
2235    }
2236
2237    @VisibleForTesting
2238    SparseArray<ShortcutUser> getShortcutsForTest() {
2239        return mUsers;
2240    }
2241
2242    @VisibleForTesting
2243    int getMaxDynamicShortcutsForTest() {
2244        return mMaxDynamicShortcuts;
2245    }
2246
2247    @VisibleForTesting
2248    int getMaxDailyUpdatesForTest() {
2249        return mMaxDailyUpdates;
2250    }
2251
2252    @VisibleForTesting
2253    long getResetIntervalForTest() {
2254        return mResetInterval;
2255    }
2256
2257    @VisibleForTesting
2258    int getMaxIconDimensionForTest() {
2259        return mMaxIconDimension;
2260    }
2261
2262    @VisibleForTesting
2263    CompressFormat getIconPersistFormatForTest() {
2264        return mIconPersistFormat;
2265    }
2266
2267    @VisibleForTesting
2268    int getIconPersistQualityForTest() {
2269        return mIconPersistQuality;
2270    }
2271
2272    @VisibleForTesting
2273    ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
2274        synchronized (mLock) {
2275            final ShortcutUser user = mUsers.get(userId);
2276            if (user == null) return null;
2277
2278            final ShortcutPackage pkg = user.getPackages().get(packageName);
2279            if (pkg == null) return null;
2280
2281            return pkg.findShortcutById(shortcutId);
2282        }
2283    }
2284}
2285