ShortcutService.java revision dd097812fa84b898a46cb5dd253422e78779a150
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.server.pm;
17
18import android.annotation.IntDef;
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.UserIdInt;
22import android.app.ActivityManager;
23import android.app.ActivityManagerNative;
24import android.app.AppGlobals;
25import android.app.IUidObserver;
26import android.app.usage.UsageStatsManagerInternal;
27import android.content.ComponentName;
28import android.content.Context;
29import android.content.Intent;
30import android.content.pm.ActivityInfo;
31import android.content.pm.ApplicationInfo;
32import android.content.pm.IPackageManager;
33import android.content.pm.IShortcutService;
34import android.content.pm.LauncherApps;
35import android.content.pm.LauncherApps.ShortcutQuery;
36import android.content.pm.PackageInfo;
37import android.content.pm.PackageManager;
38import android.content.pm.PackageManager.NameNotFoundException;
39import android.content.pm.PackageManagerInternal;
40import android.content.pm.ParceledListSlice;
41import android.content.pm.ResolveInfo;
42import android.content.pm.ShortcutInfo;
43import android.content.pm.ShortcutServiceInternal;
44import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
45import android.content.res.Resources;
46import android.content.res.XmlResourceParser;
47import android.graphics.Bitmap;
48import android.graphics.Bitmap.CompressFormat;
49import android.graphics.Canvas;
50import android.graphics.RectF;
51import android.graphics.drawable.Icon;
52import android.os.Binder;
53import android.os.Environment;
54import android.os.FileUtils;
55import android.os.Handler;
56import android.os.Looper;
57import android.os.ParcelFileDescriptor;
58import android.os.PersistableBundle;
59import android.os.Process;
60import android.os.RemoteException;
61import android.os.ResultReceiver;
62import android.os.SELinux;
63import android.os.ServiceManager;
64import android.os.ShellCommand;
65import android.os.SystemClock;
66import android.os.UserHandle;
67import android.os.UserManager;
68import android.text.TextUtils;
69import android.text.format.Time;
70import android.util.ArraySet;
71import android.util.AtomicFile;
72import android.util.KeyValueListParser;
73import android.util.Log;
74import android.util.Slog;
75import android.util.SparseArray;
76import android.util.SparseIntArray;
77import android.util.SparseLongArray;
78import android.util.TypedValue;
79import android.util.Xml;
80import android.view.IWindowManager;
81
82import com.android.internal.annotations.GuardedBy;
83import com.android.internal.annotations.VisibleForTesting;
84import com.android.internal.content.PackageMonitor;
85import com.android.internal.os.BackgroundThread;
86import com.android.internal.util.FastXmlSerializer;
87import com.android.internal.util.Preconditions;
88import com.android.server.LocalServices;
89import com.android.server.SystemService;
90import com.android.server.pm.ShortcutUser.PackageWithUser;
91
92import libcore.io.IoUtils;
93
94import org.xmlpull.v1.XmlPullParser;
95import org.xmlpull.v1.XmlPullParserException;
96import org.xmlpull.v1.XmlSerializer;
97
98import java.io.BufferedInputStream;
99import java.io.BufferedOutputStream;
100import java.io.ByteArrayInputStream;
101import java.io.ByteArrayOutputStream;
102import java.io.File;
103import java.io.FileDescriptor;
104import java.io.FileInputStream;
105import java.io.FileNotFoundException;
106import java.io.FileOutputStream;
107import java.io.IOException;
108import java.io.InputStream;
109import java.io.OutputStream;
110import java.io.PrintWriter;
111import java.lang.annotation.Retention;
112import java.lang.annotation.RetentionPolicy;
113import java.net.URISyntaxException;
114import java.nio.charset.StandardCharsets;
115import java.util.ArrayList;
116import java.util.Collections;
117import java.util.List;
118import java.util.concurrent.atomic.AtomicBoolean;
119import java.util.concurrent.atomic.AtomicLong;
120import java.util.function.Consumer;
121import java.util.function.Predicate;
122
123/**
124 * TODO:
125 * - Deal with the async nature of PACKAGE_ADD.  Basically when a publisher does anything after
126 *   it's upgraded, the manager should make sure the upgrade process has been executed.
127 *
128 * - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
129 *   -> But TypedValue.applyDimension() doesn't differentiate x and y..?
130 *
131 * - Default launcher check does take a few ms.  Worth caching.
132 *
133 * - Detect when already registered instances are passed to APIs again, which might break
134 * internal bitmap handling.
135 *
136 * - Add more call stats.
137 */
138public class ShortcutService extends IShortcutService.Stub {
139    static final String TAG = "ShortcutService";
140
141    static final boolean DEBUG = false; // STOPSHIP if true
142    static final boolean DEBUG_LOAD = false; // STOPSHIP if true
143    static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
144
145    @VisibleForTesting
146    static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
147
148    @VisibleForTesting
149    static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10;
150
151    @VisibleForTesting
152    static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5;
153
154    @VisibleForTesting
155    static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
156
157    @VisibleForTesting
158    static final int DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP = 48;
159
160    @VisibleForTesting
161    static final String DEFAULT_ICON_PERSIST_FORMAT = CompressFormat.PNG.name();
162
163    @VisibleForTesting
164    static final int DEFAULT_ICON_PERSIST_QUALITY = 100;
165
166    @VisibleForTesting
167    static final int DEFAULT_SAVE_DELAY_MS = 3000;
168
169    @VisibleForTesting
170    static final String FILENAME_BASE_STATE = "shortcut_service.xml";
171
172    @VisibleForTesting
173    static final String DIRECTORY_PER_USER = "shortcut_service";
174
175    @VisibleForTesting
176    static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
177
178    static final String DIRECTORY_BITMAPS = "bitmaps";
179
180    private static final String TAG_ROOT = "root";
181    private static final String TAG_LAST_RESET_TIME = "last_reset_time";
182    private static final String TAG_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale_seq_no";
183
184    private static final String ATTR_VALUE = "value";
185
186    private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER;
187
188    @VisibleForTesting
189    interface ConfigConstants {
190        /**
191         * Key name for the save delay, in milliseconds. (int)
192         */
193        String KEY_SAVE_DELAY_MILLIS = "save_delay_ms";
194
195        /**
196         * Key name for the throttling reset interval, in seconds. (long)
197         */
198        String KEY_RESET_INTERVAL_SEC = "reset_interval_sec";
199
200        /**
201         * Key name for the max number of modifying API calls per app for every interval. (int)
202         */
203        String KEY_MAX_UPDATES_PER_INTERVAL = "max_updates_per_interval";
204
205        /**
206         * Key name for the max icon dimensions in DP, for non-low-memory devices.
207         */
208        String KEY_MAX_ICON_DIMENSION_DP = "max_icon_dimension_dp";
209
210        /**
211         * Key name for the max icon dimensions in DP, for low-memory devices.
212         */
213        String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
214
215        /**
216         * Key name for the max dynamic shortcuts per activity. (int)
217         */
218        String KEY_MAX_SHORTCUTS = "max_shortcuts";
219
220        /**
221         * Key name for icon compression quality, 0-100.
222         */
223        String KEY_ICON_QUALITY = "icon_quality";
224
225        /**
226         * Key name for icon compression format: "PNG", "JPEG" or "WEBP"
227         */
228        String KEY_ICON_FORMAT = "icon_format";
229    }
230
231    final Context mContext;
232
233    private final Object mLock = new Object();
234
235    private final Handler mHandler;
236
237    @GuardedBy("mLock")
238    private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
239
240    @GuardedBy("mLock")
241    private long mRawLastResetTime;
242
243    /**
244     * User ID -> UserShortcuts
245     */
246    @GuardedBy("mLock")
247    private final SparseArray<ShortcutUser> mUsers = new SparseArray<>();
248
249    /**
250     * Max number of dynamic + manifest shortcuts that each application can have at a time.
251     */
252    private int mMaxShortcuts;
253
254    /**
255     * Max number of updating API calls that each application can make during the interval.
256     */
257    int mMaxUpdatesPerInterval;
258
259    /**
260     * Actual throttling-reset interval.  By default it's a day.
261     */
262    private long mResetInterval;
263
264    /**
265     * Icon max width/height in pixels.
266     */
267    private int mMaxIconDimension;
268
269    private CompressFormat mIconPersistFormat;
270    private int mIconPersistQuality;
271
272    private int mSaveDelayMillis;
273
274    private final IPackageManager mIPackageManager;
275    private final PackageManagerInternal mPackageManagerInternal;
276    private final UserManager mUserManager;
277    private final UsageStatsManagerInternal mUsageStatsManagerInternal;
278
279    @GuardedBy("mLock")
280    final SparseIntArray mUidState = new SparseIntArray();
281
282    @GuardedBy("mLock")
283    final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray();
284
285    @GuardedBy("mLock")
286    private List<Integer> mDirtyUserIds = new ArrayList<>();
287
288    /**
289     * A counter that increments every time the system locale changes.  We keep track of it to
290     * reset
291     * throttling counters on the first call from each package after the last locale change.
292     *
293     * We need this mechanism because we can't do much in the locale change callback, which is
294     * {@link ShortcutServiceInternal#onSystemLocaleChangedNoLock()}.
295     */
296    private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong();
297
298    private final AtomicBoolean mBootCompleted = new AtomicBoolean();
299
300    private static final int PACKAGE_MATCH_FLAGS =
301            PackageManager.MATCH_DIRECT_BOOT_AWARE
302                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
303                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
304
305    // Stats
306    @VisibleForTesting
307    interface Stats {
308        int GET_DEFAULT_HOME = 0;
309        int GET_PACKAGE_INFO = 1;
310        int GET_PACKAGE_INFO_WITH_SIG = 2;
311        int GET_APPLICATION_INFO = 3;
312        int LAUNCHER_PERMISSION_CHECK = 4;
313        int CLEANUP_DANGLING_BITMAPS = 5;
314        int GET_ACTIVITY_WITH_METADATA = 6;
315        int GET_INSTALLED_PACKAGES = 7;
316        int CHECK_PACKAGE_CHANGES = 8;
317        int GET_APPLICATION_RESOURCES = 9;
318        int RESOURCE_NAME_LOOKUP = 10;
319        int GET_LAUNCHER_ACTIVITY = 11;
320        int CHECK_LAUNCHER_ACTIVITY = 12;
321
322        int COUNT = CHECK_LAUNCHER_ACTIVITY + 1;
323    }
324
325    final Object mStatLock = new Object();
326
327    @GuardedBy("mStatLock")
328    private final int[] mCountStats = new int[Stats.COUNT];
329
330    @GuardedBy("mStatLock")
331    private final long[] mDurationStats = new long[Stats.COUNT];
332
333    private static final int PROCESS_STATE_FOREGROUND_THRESHOLD =
334            ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
335
336    static final int OPERATION_SET = 0;
337    static final int OPERATION_ADD = 1;
338    static final int OPERATION_UPDATE = 2;
339
340    /** @hide */
341    @IntDef(value = {
342            OPERATION_SET,
343            OPERATION_ADD,
344            OPERATION_UPDATE
345    })
346    @Retention(RetentionPolicy.SOURCE)
347    @interface ShortcutOperation {
348    }
349
350    public ShortcutService(Context context) {
351        this(context, BackgroundThread.get().getLooper());
352    }
353
354    @VisibleForTesting
355    ShortcutService(Context context, Looper looper) {
356        mContext = Preconditions.checkNotNull(context);
357        LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
358        mHandler = new Handler(looper);
359        mIPackageManager = AppGlobals.getPackageManager();
360        mPackageManagerInternal = Preconditions.checkNotNull(
361                LocalServices.getService(PackageManagerInternal.class));
362        mUserManager = Preconditions.checkNotNull(context.getSystemService(UserManager.class));
363        mUsageStatsManagerInternal = Preconditions.checkNotNull(
364                LocalServices.getService(UsageStatsManagerInternal.class));
365
366        mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
367
368        injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE
369                | ActivityManager.UID_OBSERVER_GONE);
370    }
371
372    void logDurationStat(int statId, long start) {
373        synchronized (mStatLock) {
374            mCountStats[statId]++;
375            mDurationStats[statId] += (injectElapsedRealtime() - start);
376        }
377    }
378
379    public long getLocaleChangeSequenceNumber() {
380        return mLocaleChangeSequenceNumber.get();
381    }
382
383    final private IUidObserver mUidObserver = new IUidObserver.Stub() {
384        @Override
385        public void onUidStateChanged(int uid, int procState) throws RemoteException {
386            handleOnUidStateChanged(uid, procState);
387        }
388
389        @Override
390        public void onUidGone(int uid) throws RemoteException {
391            handleOnUidStateChanged(uid, ActivityManager.MAX_PROCESS_STATE);
392        }
393
394        @Override
395        public void onUidActive(int uid) throws RemoteException {
396        }
397
398        @Override
399        public void onUidIdle(int uid) throws RemoteException {
400        }
401    };
402
403    void handleOnUidStateChanged(int uid, int procState) {
404        if (DEBUG_PROCSTATE) {
405            Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState);
406        }
407        synchronized (mLock) {
408            mUidState.put(uid, procState);
409
410            // We need to keep track of last time an app comes to foreground.
411            // See ShortcutPackage.getApiCallCount() for how it's used.
412            // It doesn't have to be persisted, but it needs to be the elapsed time.
413            if (isProcessStateForeground(procState)) {
414                mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime());
415            }
416        }
417    }
418
419    private boolean isProcessStateForeground(int processState) {
420        return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD;
421    }
422
423    boolean isUidForegroundLocked(int uid) {
424        if (uid == Process.SYSTEM_UID) {
425            // IUidObserver doesn't report the state of SYSTEM, but it always has bound services,
426            // so it's foreground anyway.
427            return true;
428        }
429        return isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE));
430    }
431
432    long getUidLastForegroundElapsedTimeLocked(int uid) {
433        return mUidLastForegroundElapsedTime.get(uid);
434    }
435
436    /**
437     * System service lifecycle.
438     */
439    public static final class Lifecycle extends SystemService {
440        final ShortcutService mService;
441
442        public Lifecycle(Context context) {
443            super(context);
444            mService = new ShortcutService(context);
445        }
446
447        @Override
448        public void onStart() {
449            publishBinderService(Context.SHORTCUT_SERVICE, mService);
450        }
451
452        @Override
453        public void onBootPhase(int phase) {
454            mService.onBootPhase(phase);
455        }
456
457        @Override
458        public void onCleanupUser(int userHandle) {
459            mService.handleCleanupUser(userHandle);
460        }
461
462        @Override
463        public void onUnlockUser(int userId) {
464            mService.handleUnlockUser(userId);
465        }
466    }
467
468    /** lifecycle event */
469    void onBootPhase(int phase) {
470        if (DEBUG) {
471            Slog.d(TAG, "onBootPhase: " + phase);
472        }
473        switch (phase) {
474            case SystemService.PHASE_LOCK_SETTINGS_READY:
475                initialize();
476                break;
477            case SystemService.PHASE_BOOT_COMPLETED:
478                mBootCompleted.set(true);
479                break;
480        }
481    }
482
483    /** lifecycle event */
484    void handleUnlockUser(int userId) {
485        if (DEBUG) {
486            Slog.d(TAG, "handleUnlockUser: user=" + userId);
487        }
488        synchronized (mLock) {
489            // Preload
490            getUserShortcutsLocked(userId);
491
492            checkPackageChanges(userId);
493        }
494    }
495
496    /** lifecycle event */
497    void handleCleanupUser(int userId) {
498        synchronized (mLock) {
499            unloadUserLocked(userId);
500        }
501    }
502
503    private void unloadUserLocked(int userId) {
504        if (DEBUG) {
505            Slog.d(TAG, "unloadUserLocked: user=" + userId);
506        }
507        // Save all dirty information.
508        saveDirtyInfo();
509
510        // Unload
511        mUsers.delete(userId);
512    }
513
514    /** Return the base state file name */
515    private AtomicFile getBaseStateFile() {
516        final File path = new File(injectSystemDataPath(), FILENAME_BASE_STATE);
517        path.mkdirs();
518        return new AtomicFile(path);
519    }
520
521    /**
522     * Init the instance. (load the state file, etc)
523     */
524    private void initialize() {
525        synchronized (mLock) {
526            loadConfigurationLocked();
527            loadBaseStateLocked();
528        }
529    }
530
531    /**
532     * Load the configuration from Settings.
533     */
534    private void loadConfigurationLocked() {
535        updateConfigurationLocked(injectShortcutManagerConstants());
536    }
537
538    /**
539     * Load the configuration from Settings.
540     */
541    @VisibleForTesting
542    boolean updateConfigurationLocked(String config) {
543        boolean result = true;
544
545        final KeyValueListParser parser = new KeyValueListParser(',');
546        try {
547            parser.setString(config);
548        } catch (IllegalArgumentException e) {
549            // Failed to parse the settings string, log this and move on
550            // with defaults.
551            Slog.e(TAG, "Bad shortcut manager settings", e);
552            result = false;
553        }
554
555        mSaveDelayMillis = Math.max(0, (int) parser.getLong(ConfigConstants.KEY_SAVE_DELAY_MILLIS,
556                DEFAULT_SAVE_DELAY_MS));
557
558        mResetInterval = Math.max(1, parser.getLong(
559                ConfigConstants.KEY_RESET_INTERVAL_SEC, DEFAULT_RESET_INTERVAL_SEC)
560                * 1000L);
561
562        mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
563                ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
564
565        mMaxShortcuts = Math.max(0, (int) parser.getLong(
566                ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_APP));
567
568        final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
569                ? (int) parser.getLong(
570                ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
571                DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
572                : (int) parser.getLong(
573                ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
574                DEFAULT_MAX_ICON_DIMENSION_DP));
575
576        mMaxIconDimension = injectDipToPixel(iconDimensionDp);
577
578        mIconPersistFormat = CompressFormat.valueOf(
579                parser.getString(ConfigConstants.KEY_ICON_FORMAT, DEFAULT_ICON_PERSIST_FORMAT));
580
581        mIconPersistQuality = (int) parser.getLong(
582                ConfigConstants.KEY_ICON_QUALITY,
583                DEFAULT_ICON_PERSIST_QUALITY);
584
585        return result;
586    }
587
588    @VisibleForTesting
589    String injectShortcutManagerConstants() {
590        return android.provider.Settings.Global.getString(
591                mContext.getContentResolver(),
592                android.provider.Settings.Global.SHORTCUT_MANAGER_CONSTANTS);
593    }
594
595    @VisibleForTesting
596    int injectDipToPixel(int dip) {
597        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
598                mContext.getResources().getDisplayMetrics());
599    }
600
601    // === Persisting ===
602
603    @Nullable
604    static String parseStringAttribute(XmlPullParser parser, String attribute) {
605        return parser.getAttributeValue(null, attribute);
606    }
607
608    static boolean parseBooleanAttribute(XmlPullParser parser, String attribute) {
609        return parseLongAttribute(parser, attribute) == 1;
610    }
611
612    static int parseIntAttribute(XmlPullParser parser, String attribute) {
613        return (int) parseLongAttribute(parser, attribute);
614    }
615
616    static int parseIntAttribute(XmlPullParser parser, String attribute, int def) {
617        return (int) parseLongAttribute(parser, attribute, def);
618    }
619
620    static long parseLongAttribute(XmlPullParser parser, String attribute) {
621        return parseLongAttribute(parser, attribute, 0);
622    }
623
624    static long parseLongAttribute(XmlPullParser parser, String attribute, long def) {
625        final String value = parseStringAttribute(parser, attribute);
626        if (TextUtils.isEmpty(value)) {
627            return def;
628        }
629        try {
630            return Long.parseLong(value);
631        } catch (NumberFormatException e) {
632            Slog.e(TAG, "Error parsing long " + value);
633            return def;
634        }
635    }
636
637    @Nullable
638    static ComponentName parseComponentNameAttribute(XmlPullParser parser, String attribute) {
639        final String value = parseStringAttribute(parser, attribute);
640        if (TextUtils.isEmpty(value)) {
641            return null;
642        }
643        return ComponentName.unflattenFromString(value);
644    }
645
646    @Nullable
647    static Intent parseIntentAttribute(XmlPullParser parser, String attribute) {
648        final String value = parseStringAttribute(parser, attribute);
649        Intent parsed = null;
650        if (!TextUtils.isEmpty(value)) {
651            try {
652                parsed = Intent.parseUri(value, /* flags =*/ 0);
653            } catch (URISyntaxException e) {
654                Slog.e(TAG, "Error parsing intent", e);
655            }
656        }
657        if (parsed == null) {
658            // Default intent.
659            parsed = new Intent(Intent.ACTION_VIEW);
660        }
661        return parsed;
662    }
663
664    static void writeTagValue(XmlSerializer out, String tag, String value) throws IOException {
665        if (TextUtils.isEmpty(value)) return;
666
667        out.startTag(null, tag);
668        out.attribute(null, ATTR_VALUE, value);
669        out.endTag(null, tag);
670    }
671
672    static void writeTagValue(XmlSerializer out, String tag, long value) throws IOException {
673        writeTagValue(out, tag, Long.toString(value));
674    }
675
676    static void writeTagValue(XmlSerializer out, String tag, ComponentName name) throws IOException {
677        if (name == null) return;
678        writeTagValue(out, tag, name.flattenToString());
679    }
680
681    static void writeTagExtra(XmlSerializer out, String tag, PersistableBundle bundle)
682            throws IOException, XmlPullParserException {
683        if (bundle == null) return;
684
685        out.startTag(null, tag);
686        bundle.saveToXml(out);
687        out.endTag(null, tag);
688    }
689
690    static void writeAttr(XmlSerializer out, String name, CharSequence value) throws IOException {
691        if (TextUtils.isEmpty(value)) return;
692
693        out.attribute(null, name, value.toString());
694    }
695
696    static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
697        writeAttr(out, name, String.valueOf(value));
698    }
699
700    static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
701        if (value) {
702            writeAttr(out, name, "1");
703        }
704    }
705
706    static void writeAttr(XmlSerializer out, String name, ComponentName comp) throws IOException {
707        if (comp == null) return;
708        writeAttr(out, name, comp.flattenToString());
709    }
710
711    static void writeAttr(XmlSerializer out, String name, Intent intent) throws IOException {
712        if (intent == null) return;
713
714        writeAttr(out, name, intent.toUri(/* flags =*/ 0));
715    }
716
717    @VisibleForTesting
718    void saveBaseStateLocked() {
719        final AtomicFile file = getBaseStateFile();
720        if (DEBUG) {
721            Slog.d(TAG, "Saving to " + file.getBaseFile());
722        }
723
724        FileOutputStream outs = null;
725        try {
726            outs = file.startWrite();
727
728            // Write to XML
729            XmlSerializer out = new FastXmlSerializer();
730            out.setOutput(outs, StandardCharsets.UTF_8.name());
731            out.startDocument(null, true);
732            out.startTag(null, TAG_ROOT);
733
734            // Body.
735            writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime);
736            writeTagValue(out, TAG_LOCALE_CHANGE_SEQUENCE_NUMBER,
737                    mLocaleChangeSequenceNumber.get());
738
739            // Epilogue.
740            out.endTag(null, TAG_ROOT);
741            out.endDocument();
742
743            // Close.
744            file.finishWrite(outs);
745        } catch (IOException e) {
746            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
747            file.failWrite(outs);
748        }
749    }
750
751    private void loadBaseStateLocked() {
752        mRawLastResetTime = 0;
753
754        final AtomicFile file = getBaseStateFile();
755        if (DEBUG) {
756            Slog.d(TAG, "Loading from " + file.getBaseFile());
757        }
758        try (FileInputStream in = file.openRead()) {
759            XmlPullParser parser = Xml.newPullParser();
760            parser.setInput(in, StandardCharsets.UTF_8.name());
761
762            int type;
763            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
764                if (type != XmlPullParser.START_TAG) {
765                    continue;
766                }
767                final int depth = parser.getDepth();
768                // Check the root tag
769                final String tag = parser.getName();
770                if (depth == 1) {
771                    if (!TAG_ROOT.equals(tag)) {
772                        Slog.e(TAG, "Invalid root tag: " + tag);
773                        return;
774                    }
775                    continue;
776                }
777                // Assume depth == 2
778                switch (tag) {
779                    case TAG_LAST_RESET_TIME:
780                        mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE);
781                        break;
782                    case TAG_LOCALE_CHANGE_SEQUENCE_NUMBER:
783                        mLocaleChangeSequenceNumber.set(parseLongAttribute(parser, ATTR_VALUE));
784                        break;
785                    default:
786                        Slog.e(TAG, "Invalid tag: " + tag);
787                        break;
788                }
789            }
790        } catch (FileNotFoundException e) {
791            // Use the default
792        } catch (IOException | XmlPullParserException e) {
793            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
794
795            mRawLastResetTime = 0;
796        }
797        // Adjust the last reset time.
798        getLastResetTimeLocked();
799    }
800
801    private void saveUserLocked(@UserIdInt int userId) {
802        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
803        if (DEBUG) {
804            Slog.d(TAG, "Saving to " + path);
805        }
806        path.mkdirs();
807        final AtomicFile file = new AtomicFile(path);
808        FileOutputStream os = null;
809        try {
810            os = file.startWrite();
811
812            saveUserInternalLocked(userId, os, /* forBackup= */ false);
813
814            file.finishWrite(os);
815        } catch (XmlPullParserException | IOException e) {
816            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
817            file.failWrite(os);
818        }
819    }
820
821    private void saveUserInternalLocked(@UserIdInt int userId, OutputStream os,
822            boolean forBackup) throws IOException, XmlPullParserException {
823
824        final BufferedOutputStream bos = new BufferedOutputStream(os);
825
826        // Write to XML
827        XmlSerializer out = new FastXmlSerializer();
828        out.setOutput(bos, StandardCharsets.UTF_8.name());
829        out.startDocument(null, true);
830
831        getUserShortcutsLocked(userId).saveToXml(out, forBackup);
832
833        out.endDocument();
834
835        bos.flush();
836        os.flush();
837    }
838
839    static IOException throwForInvalidTag(int depth, String tag) throws IOException {
840        throw new IOException(String.format("Invalid tag '%s' found at depth %d", tag, depth));
841    }
842
843    static void warnForInvalidTag(int depth, String tag) throws IOException {
844        Slog.w(TAG, String.format("Invalid tag '%s' found at depth %d", tag, depth));
845    }
846
847    @Nullable
848    private ShortcutUser loadUserLocked(@UserIdInt int userId) {
849        final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
850        if (DEBUG) {
851            Slog.d(TAG, "Loading from " + path);
852        }
853        final AtomicFile file = new AtomicFile(path);
854
855        final FileInputStream in;
856        try {
857            in = file.openRead();
858        } catch (FileNotFoundException e) {
859            if (DEBUG) {
860                Slog.d(TAG, "Not found " + path);
861            }
862            return null;
863        }
864        try {
865            final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
866            cleanupDanglingBitmapDirectoriesLocked(userId, ret);
867            return ret;
868        } catch (IOException | XmlPullParserException e) {
869            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
870            return null;
871        } finally {
872            IoUtils.closeQuietly(in);
873        }
874    }
875
876    private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
877            boolean fromBackup) throws XmlPullParserException, IOException {
878
879        final BufferedInputStream bis = new BufferedInputStream(is);
880
881        ShortcutUser ret = null;
882        XmlPullParser parser = Xml.newPullParser();
883        parser.setInput(bis, StandardCharsets.UTF_8.name());
884
885        int type;
886        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
887            if (type != XmlPullParser.START_TAG) {
888                continue;
889            }
890            final int depth = parser.getDepth();
891
892            final String tag = parser.getName();
893            if (DEBUG_LOAD) {
894                Slog.d(TAG, String.format("depth=%d type=%d name=%s",
895                        depth, type, tag));
896            }
897            if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) {
898                ret = ShortcutUser.loadFromXml(this, parser, userId, fromBackup);
899                continue;
900            }
901            throwForInvalidTag(depth, tag);
902        }
903        return ret;
904    }
905
906    private void scheduleSaveBaseState() {
907        scheduleSaveInner(UserHandle.USER_NULL); // Special case -- use USER_NULL for base state.
908    }
909
910    void scheduleSaveUser(@UserIdInt int userId) {
911        scheduleSaveInner(userId);
912    }
913
914    // In order to re-schedule, we need to reuse the same instance, so keep it in final.
915    private final Runnable mSaveDirtyInfoRunner = this::saveDirtyInfo;
916
917    private void scheduleSaveInner(@UserIdInt int userId) {
918        if (DEBUG) {
919            Slog.d(TAG, "Scheduling to save for " + userId);
920        }
921        synchronized (mLock) {
922            if (!mDirtyUserIds.contains(userId)) {
923                mDirtyUserIds.add(userId);
924            }
925        }
926        // If already scheduled, remove that and re-schedule in N seconds.
927        mHandler.removeCallbacks(mSaveDirtyInfoRunner);
928        mHandler.postDelayed(mSaveDirtyInfoRunner, mSaveDelayMillis);
929    }
930
931    @VisibleForTesting
932    void saveDirtyInfo() {
933        if (DEBUG) {
934            Slog.d(TAG, "saveDirtyInfo");
935        }
936        synchronized (mLock) {
937            for (int i = mDirtyUserIds.size() - 1; i >= 0; i--) {
938                final int userId = mDirtyUserIds.get(i);
939                if (userId == UserHandle.USER_NULL) { // USER_NULL for base state.
940                    saveBaseStateLocked();
941                } else {
942                    saveUserLocked(userId);
943                }
944            }
945            mDirtyUserIds.clear();
946        }
947    }
948
949    /** Return the last reset time. */
950    long getLastResetTimeLocked() {
951        updateTimesLocked();
952        return mRawLastResetTime;
953    }
954
955    /** Return the next reset time. */
956    long getNextResetTimeLocked() {
957        updateTimesLocked();
958        return mRawLastResetTime + mResetInterval;
959    }
960
961    static boolean isClockValid(long time) {
962        return time >= 1420070400; // Thu, 01 Jan 2015 00:00:00 GMT
963    }
964
965    /**
966     * Update the last reset time.
967     */
968    private void updateTimesLocked() {
969
970        final long now = injectCurrentTimeMillis();
971
972        final long prevLastResetTime = mRawLastResetTime;
973
974        if (mRawLastResetTime == 0) { // first launch.
975            // TODO Randomize??
976            mRawLastResetTime = now;
977        } else if (now < mRawLastResetTime) {
978            // Clock rewound.
979            if (isClockValid(now)) {
980                Slog.w(TAG, "Clock rewound");
981                // TODO Randomize??
982                mRawLastResetTime = now;
983            }
984        } else {
985            if ((mRawLastResetTime + mResetInterval) <= now) {
986                final long offset = mRawLastResetTime % mResetInterval;
987                mRawLastResetTime = ((now / mResetInterval) * mResetInterval) + offset;
988            }
989        }
990        if (prevLastResetTime != mRawLastResetTime) {
991            scheduleSaveBaseState();
992        }
993    }
994
995    @GuardedBy("mLock")
996    @NonNull
997    private boolean isUserLoadedLocked(@UserIdInt int userId) {
998        return mUsers.get(userId) != null;
999    }
1000
1001    /** Return the per-user state. */
1002    @GuardedBy("mLock")
1003    @NonNull
1004    ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) {
1005        ShortcutUser userPackages = mUsers.get(userId);
1006        if (userPackages == null) {
1007            userPackages = loadUserLocked(userId);
1008            if (userPackages == null) {
1009                userPackages = new ShortcutUser(this, userId);
1010            }
1011            mUsers.put(userId, userPackages);
1012        }
1013        return userPackages;
1014    }
1015
1016    void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
1017        for (int i = mUsers.size() - 1; i >= 0; i--) {
1018            c.accept(mUsers.valueAt(i));
1019        }
1020    }
1021
1022    /** Return the per-user per-package state. */
1023    @GuardedBy("mLock")
1024    @NonNull
1025    ShortcutPackage getPackageShortcutsLocked(
1026            @NonNull String packageName, @UserIdInt int userId) {
1027        return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
1028    }
1029
1030    @GuardedBy("mLock")
1031    @NonNull
1032    ShortcutLauncher getLauncherShortcutsLocked(
1033            @NonNull String packageName, @UserIdInt int ownerUserId,
1034            @UserIdInt int launcherUserId) {
1035        return getUserShortcutsLocked(ownerUserId)
1036                .getLauncherShortcuts(packageName, launcherUserId);
1037    }
1038
1039    // === Caller validation ===
1040
1041    void removeIcon(@UserIdInt int userId, ShortcutInfo shortcut) {
1042        if (shortcut.getBitmapPath() != null) {
1043            if (DEBUG) {
1044                Slog.d(TAG, "Removing " + shortcut.getBitmapPath());
1045            }
1046            new File(shortcut.getBitmapPath()).delete();
1047
1048            shortcut.setBitmapPath(null);
1049        }
1050        shortcut.setIconResourceId(0);
1051        shortcut.setIconResName(null);
1052        shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES);
1053    }
1054
1055    public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) {
1056        final File packagePath = new File(getUserBitmapFilePath(userId), packageName);
1057        if (!packagePath.isDirectory()) {
1058            return;
1059        }
1060        if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) {
1061            Slog.w(TAG, "Unable to remove directory " + packagePath);
1062        }
1063    }
1064
1065    private void cleanupDanglingBitmapDirectoriesLocked(
1066            @UserIdInt int userId, @NonNull ShortcutUser user) {
1067        if (DEBUG) {
1068            Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
1069        }
1070        final long start = injectElapsedRealtime();
1071
1072        final File bitmapDir = getUserBitmapFilePath(userId);
1073        final File[] children = bitmapDir.listFiles();
1074        if (children == null) {
1075            return;
1076        }
1077        for (File child : children) {
1078            if (!child.isDirectory()) {
1079                continue;
1080            }
1081            final String packageName = child.getName();
1082            if (DEBUG) {
1083                Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName);
1084            }
1085            if (!user.hasPackage(packageName)) {
1086                if (DEBUG) {
1087                    Slog.d(TAG, "Removing dangling bitmap directory: " + packageName);
1088                }
1089                cleanupBitmapsForPackage(userId, packageName);
1090            } else {
1091                cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
1092            }
1093        }
1094        logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
1095    }
1096
1097    private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
1098            @NonNull String packageName, @NonNull File path) {
1099        final ArraySet<String> usedFiles =
1100                user.getPackageShortcuts(packageName).getUsedBitmapFiles();
1101
1102        for (File child : path.listFiles()) {
1103            if (!child.isFile()) {
1104                continue;
1105            }
1106            final String name = child.getName();
1107            if (!usedFiles.contains(name)) {
1108                if (DEBUG) {
1109                    Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
1110                }
1111                child.delete();
1112            }
1113        }
1114    }
1115
1116    @VisibleForTesting
1117    static class FileOutputStreamWithPath extends FileOutputStream {
1118        private final File mFile;
1119
1120        public FileOutputStreamWithPath(File file) throws FileNotFoundException {
1121            super(file);
1122            mFile = file;
1123        }
1124
1125        public File getFile() {
1126            return mFile;
1127        }
1128    }
1129
1130    /**
1131     * Build the cached bitmap filename for a shortcut icon.
1132     *
1133     * The filename will be based on the ID, except certain characters will be escaped.
1134     */
1135    @VisibleForTesting
1136    FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
1137            throws IOException {
1138        final File packagePath = new File(getUserBitmapFilePath(userId),
1139                shortcut.getPackage());
1140        if (!packagePath.isDirectory()) {
1141            packagePath.mkdirs();
1142            if (!packagePath.isDirectory()) {
1143                throw new IOException("Unable to create directory " + packagePath);
1144            }
1145            SELinux.restorecon(packagePath);
1146        }
1147
1148        final String baseName = String.valueOf(injectCurrentTimeMillis());
1149        for (int suffix = 0; ; suffix++) {
1150            final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
1151            final File file = new File(packagePath, filename);
1152            if (!file.exists()) {
1153                if (DEBUG) {
1154                    Slog.d(TAG, "Saving icon to " + file.getAbsolutePath());
1155                }
1156                return new FileOutputStreamWithPath(file);
1157            }
1158        }
1159    }
1160
1161    void saveIconAndFixUpShortcut(@UserIdInt int userId, ShortcutInfo shortcut) {
1162        if (shortcut.hasIconFile() || shortcut.hasIconResource()) {
1163            return;
1164        }
1165
1166        final long token = injectClearCallingIdentity();
1167        try {
1168            // Clear icon info on the shortcut.
1169            removeIcon(userId, shortcut);
1170
1171            final Icon icon = shortcut.getIcon();
1172            if (icon == null) {
1173                return; // has no icon
1174            }
1175
1176            Bitmap bitmap;
1177            try {
1178                switch (icon.getType()) {
1179                    case Icon.TYPE_RESOURCE: {
1180                        injectValidateIconResPackage(shortcut, icon);
1181
1182                        shortcut.setIconResourceId(icon.getResId());
1183                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES);
1184                        return;
1185                    }
1186                    case Icon.TYPE_BITMAP: {
1187                        bitmap = icon.getBitmap(); // Don't recycle in this case.
1188                        break;
1189                    }
1190                    default:
1191                        // This shouldn't happen because we've already validated the icon, but
1192                        // just in case.
1193                        throw ShortcutInfo.getInvalidIconException();
1194                }
1195                if (bitmap == null) {
1196                    Slog.e(TAG, "Null bitmap detected");
1197                    return;
1198                }
1199                // Shrink and write to the file.
1200                File path = null;
1201                try {
1202                    final FileOutputStreamWithPath out = openIconFileForWrite(userId, shortcut);
1203                    try {
1204                        path = out.getFile();
1205
1206                        Bitmap shrunk = shrinkBitmap(bitmap, mMaxIconDimension);
1207                        try {
1208                            shrunk.compress(mIconPersistFormat, mIconPersistQuality, out);
1209                        } finally {
1210                            if (bitmap != shrunk) {
1211                                shrunk.recycle();
1212                            }
1213                        }
1214
1215                        shortcut.setBitmapPath(out.getFile().getAbsolutePath());
1216                        shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE);
1217                    } finally {
1218                        IoUtils.closeQuietly(out);
1219                    }
1220                } catch (IOException | RuntimeException e) {
1221                    // STOPSHIP Change wtf to e
1222                    Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
1223                    if (path != null && path.exists()) {
1224                        path.delete();
1225                    }
1226                }
1227            } finally {
1228                // Once saved, we won't use the original icon information, so null it out.
1229                shortcut.clearIcon();
1230            }
1231        } finally {
1232            injectRestoreCallingIdentity(token);
1233        }
1234    }
1235
1236    // Unfortunately we can't do this check in unit tests because we fake creator package names,
1237    // so override in unit tests.
1238    // TODO CTS this case.
1239    void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
1240        if (!shortcut.getPackage().equals(icon.getResPackage())) {
1241            throw new IllegalArgumentException(
1242                    "Icon resource must reside in shortcut owner package");
1243        }
1244    }
1245
1246    @VisibleForTesting
1247    static Bitmap shrinkBitmap(Bitmap in, int maxSize) {
1248        // Original width/height.
1249        final int ow = in.getWidth();
1250        final int oh = in.getHeight();
1251        if ((ow <= maxSize) && (oh <= maxSize)) {
1252            if (DEBUG) {
1253                Slog.d(TAG, String.format("Icon size %dx%d, no need to shrink", ow, oh));
1254            }
1255            return in;
1256        }
1257        final int longerDimension = Math.max(ow, oh);
1258
1259        // New width and height.
1260        final int nw = ow * maxSize / longerDimension;
1261        final int nh = oh * maxSize / longerDimension;
1262        if (DEBUG) {
1263            Slog.d(TAG, String.format("Icon size %dx%d, shrinking to %dx%d",
1264                    ow, oh, nw, nh));
1265        }
1266
1267        final Bitmap scaledBitmap = Bitmap.createBitmap(nw, nh, Bitmap.Config.ARGB_8888);
1268        final Canvas c = new Canvas(scaledBitmap);
1269
1270        final RectF dst = new RectF(0, 0, nw, nh);
1271
1272        c.drawBitmap(in, /*src=*/ null, dst, /* paint =*/ null);
1273
1274        return scaledBitmap;
1275    }
1276
1277    /**
1278     * For a shortcut, update all resource names from resource IDs, and also update all
1279     * resource-based strings.
1280     */
1281    void fixUpShortcutResourceNamesAndValues(ShortcutInfo si) {
1282        final Resources publisherRes = injectGetResourcesForApplicationAsUser(
1283                si.getPackage(), si.getUserId());
1284        if (publisherRes != null) {
1285            final long start = injectElapsedRealtime();
1286            try {
1287                si.lookupAndFillInResourceNames(publisherRes);
1288            } finally {
1289                logDurationStat(Stats.RESOURCE_NAME_LOOKUP, start);
1290            }
1291            si.resolveResourceStrings(publisherRes);
1292        }
1293    }
1294
1295    // === Caller validation ===
1296
1297    private boolean isCallerSystem() {
1298        final int callingUid = injectBinderCallingUid();
1299        return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
1300    }
1301
1302    private boolean isCallerShell() {
1303        final int callingUid = injectBinderCallingUid();
1304        return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
1305    }
1306
1307    private void enforceSystemOrShell() {
1308        Preconditions.checkState(isCallerSystem() || isCallerShell(),
1309                "Caller must be system or shell");
1310    }
1311
1312    private void enforceShell() {
1313        Preconditions.checkState(isCallerShell(), "Caller must be shell");
1314    }
1315
1316    private void enforceSystem() {
1317        Preconditions.checkState(isCallerSystem(), "Caller must be system");
1318    }
1319
1320    private void enforceResetThrottlingPermission() {
1321        if (isCallerSystem()) {
1322            return;
1323        }
1324        injectEnforceCallingPermission(
1325                android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null);
1326    }
1327
1328    /**
1329     * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse
1330     * mockito.  So instead we extracted it here and override it in the tests.
1331     */
1332    @VisibleForTesting
1333    void injectEnforceCallingPermission(
1334            @NonNull String permission, @Nullable String message) {
1335        mContext.enforceCallingPermission(permission, message);
1336    }
1337
1338    private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
1339        Preconditions.checkStringNotEmpty(packageName, "packageName");
1340
1341        if (isCallerSystem()) {
1342            return; // no check
1343        }
1344
1345        final int callingUid = injectBinderCallingUid();
1346
1347        // Otherwise, make sure the arguments are valid.
1348        if (UserHandle.getUserId(callingUid) != userId) {
1349            throw new SecurityException("Invalid user-ID");
1350        }
1351        if (injectGetPackageUid(packageName, userId) == injectBinderCallingUid()) {
1352            return; // Caller is valid.
1353        }
1354        throw new SecurityException("Calling package name mismatch");
1355    }
1356
1357    // Overridden in unit tests to execute r synchronously.
1358    void injectPostToHandler(Runnable r) {
1359        mHandler.post(r);
1360    }
1361
1362    /**
1363     * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
1364     *                                  {@link #getMaxActivityShortcuts()}.
1365     */
1366    void enforceMaxActivityShortcuts(int numShortcuts) {
1367        if (numShortcuts > mMaxShortcuts) {
1368            throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
1369        }
1370    }
1371
1372    /**
1373     * Return the max number of dynamic + manifest shortcuts for each launcher icon.
1374     */
1375    int getMaxActivityShortcuts() {
1376        return mMaxShortcuts;
1377    }
1378
1379    /**
1380     * - Sends a notification to LauncherApps
1381     * - Write to file
1382     */
1383    void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) {
1384        if (DEBUG) {
1385            Slog.d(TAG, String.format(
1386                    "Shortcut changes: package=%s, user=%d", packageName, userId));
1387        }
1388        notifyListeners(packageName, userId);
1389        scheduleSaveUser(userId);
1390    }
1391
1392    private void notifyListeners(@NonNull String packageName, @UserIdInt int userId) {
1393        final long token = injectClearCallingIdentity();
1394        try {
1395            if (!mUserManager.isUserRunning(userId)) {
1396                return;
1397            }
1398        } finally {
1399            injectRestoreCallingIdentity(token);
1400        }
1401        injectPostToHandler(() -> {
1402            final ArrayList<ShortcutChangeListener> copy;
1403            synchronized (mLock) {
1404                copy = new ArrayList<>(mListeners);
1405            }
1406            // Note onShortcutChanged() needs to be called with the system service permissions.
1407            for (int i = copy.size() - 1; i >= 0; i--) {
1408                copy.get(i).onShortcutChanged(packageName, userId);
1409            }
1410        });
1411    }
1412
1413    /**
1414     * Clean up / validate an incoming shortcut.
1415     * - Make sure all mandatory fields are set.
1416     * - Make sure the intent's extras are persistable, and them to set
1417     * {@link ShortcutInfo#mIntentPersistableExtras}.  Also clear its extras.
1418     * - Clear flags.
1419     *
1420     * TODO Detailed unit tests
1421     */
1422    private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
1423        Preconditions.checkNotNull(shortcut, "Null shortcut detected");
1424        if (shortcut.getActivity() != null) {
1425            Preconditions.checkState(
1426                    shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
1427                    "Cannot publish shortcut: activity " + shortcut.getActivity() + " does not"
1428                    + " belong to package " + shortcut.getPackage());
1429        }
1430
1431        if (!forUpdate) {
1432            shortcut.enforceMandatoryFields();
1433            Preconditions.checkArgument(
1434                    injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()),
1435                    "Cannot publish shortcut: " + shortcut.getActivity() + " is not main activity");
1436        }
1437        if (shortcut.getIcon() != null) {
1438            ShortcutInfo.validateIcon(shortcut.getIcon());
1439        }
1440
1441        shortcut.replaceFlags(0);
1442    }
1443
1444    /**
1445     * When a shortcut has no target activity, set the default one from the package.
1446     */
1447    private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) {
1448
1449        ComponentName defaultActivity = null;
1450        for (int i = shortcuts.size() - 1; i >= 0; i--) {
1451            final ShortcutInfo si = shortcuts.get(i);
1452            if (si.getActivity() == null) {
1453                if (defaultActivity == null) {
1454                    defaultActivity = injectGetDefaultMainActivity(
1455                            si.getPackage(), si.getUserId());
1456                    Preconditions.checkState(defaultActivity != null,
1457                            "Launcher activity not found for package " + si.getPackage());
1458                }
1459                si.setActivity(defaultActivity);
1460            }
1461        }
1462    }
1463
1464    private void assignImplicitRanks(List<ShortcutInfo> shortcuts) {
1465        for (int i = shortcuts.size() - 1; i >= 0; i--) {
1466            shortcuts.get(i).setImplicitRank(i);
1467        }
1468    }
1469
1470    // === APIs ===
1471
1472    @Override
1473    public boolean setDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1474            @UserIdInt int userId) {
1475        verifyCaller(packageName, userId);
1476
1477        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1478        final int size = newShortcuts.size();
1479
1480        synchronized (mLock) {
1481            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1482
1483            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1484
1485            fillInDefaultActivity(newShortcuts);
1486
1487            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
1488
1489            // Throttling.
1490            if (!ps.tryApiCall()) {
1491                return false;
1492            }
1493
1494            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1495            ps.clearAllImplicitRanks();
1496            assignImplicitRanks(newShortcuts);
1497
1498            for (int i = 0; i < size; i++) {
1499                fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
1500            }
1501
1502            // First, remove all un-pinned; dynamic shortcuts
1503            ps.deleteAllDynamicShortcuts();
1504
1505            // Then, add/update all.  We need to make sure to take over "pinned" flag.
1506            for (int i = 0; i < size; i++) {
1507                final ShortcutInfo newShortcut = newShortcuts.get(i);
1508                ps.addOrUpdateDynamicShortcut(newShortcut);
1509            }
1510
1511            // Lastly, adjust the ranks.
1512            ps.adjustRanks();
1513        }
1514        packageShortcutsChanged(packageName, userId);
1515
1516        verifyStates();
1517
1518        return true;
1519    }
1520
1521    @Override
1522    public boolean updateShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1523            @UserIdInt int userId) {
1524        verifyCaller(packageName, userId);
1525
1526        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1527        final int size = newShortcuts.size();
1528
1529        synchronized (mLock) {
1530            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1531
1532            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1533
1534            // For update, don't fill in the default activity.  Having null activity means
1535            // "don't update the activity" here.
1536
1537            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
1538
1539            // Throttling.
1540            if (!ps.tryApiCall()) {
1541                return false;
1542            }
1543
1544            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1545            ps.clearAllImplicitRanks();
1546            assignImplicitRanks(newShortcuts);
1547
1548            for (int i = 0; i < size; i++) {
1549                final ShortcutInfo source = newShortcuts.get(i);
1550                fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
1551
1552                final ShortcutInfo target = ps.findShortcutById(source.getId());
1553                if (target == null) {
1554                    continue;
1555                }
1556
1557                if (target.isEnabled() != source.isEnabled()) {
1558                    Slog.w(TAG,
1559                            "ShortcutInfo.enabled cannot be changed with updateShortcuts()");
1560                }
1561
1562                // When updating the rank, we need to insert between existing ranks, so set
1563                // this setRankChanged, and also copy the implicit rank fo adjustRanks().
1564                if (source.hasRank()) {
1565                    target.setRankChanged();
1566                    target.setImplicitRank(source.getImplicitRank());
1567                }
1568
1569                final boolean replacingIcon = (source.getIcon() != null);
1570                if (replacingIcon) {
1571                    removeIcon(userId, target);
1572                }
1573
1574                if (source.getActivity() != null &&
1575                        !source.getActivity().equals(target.getActivity())) {
1576                    // TODO When activity is changing, check the dynamic count.
1577                }
1578
1579                // Note copyNonNullFieldsFrom() does the "updatable with?" check too.
1580                target.copyNonNullFieldsFrom(source);
1581                target.setTimestamp(injectCurrentTimeMillis());
1582
1583                if (replacingIcon) {
1584                    saveIconAndFixUpShortcut(userId, target);
1585                }
1586
1587                // When we're updating any resource related fields, re-extract the res names and
1588                // the values.
1589                if (replacingIcon || source.hasStringResources()) {
1590                    fixUpShortcutResourceNamesAndValues(target);
1591                }
1592            }
1593
1594            // Lastly, adjust the ranks.
1595            ps.adjustRanks();
1596        }
1597        packageShortcutsChanged(packageName, userId);
1598
1599        verifyStates();
1600
1601        return true;
1602    }
1603
1604    @Override
1605    public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
1606            @UserIdInt int userId) {
1607        verifyCaller(packageName, userId);
1608
1609        final List<ShortcutInfo> newShortcuts = (List<ShortcutInfo>) shortcutInfoList.getList();
1610        final int size = newShortcuts.size();
1611
1612        synchronized (mLock) {
1613            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1614
1615            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
1616
1617            fillInDefaultActivity(newShortcuts);
1618
1619            ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
1620
1621            // Initialize the implicit ranks for ShortcutPackage.adjustRanks().
1622            ps.clearAllImplicitRanks();
1623            assignImplicitRanks(newShortcuts);
1624
1625            // Throttling.
1626            if (!ps.tryApiCall()) {
1627                return false;
1628            }
1629            for (int i = 0; i < size; i++) {
1630                final ShortcutInfo newShortcut = newShortcuts.get(i);
1631
1632                // Validate the shortcut.
1633                fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
1634
1635                // When ranks are changing, we need to insert between ranks, so set the
1636                // "rank changed" flag.
1637                newShortcut.setRankChanged();
1638
1639                // Add it.
1640                ps.addOrUpdateDynamicShortcut(newShortcut);
1641            }
1642
1643            // Lastly, adjust the ranks.
1644            ps.adjustRanks();
1645        }
1646        packageShortcutsChanged(packageName, userId);
1647
1648        verifyStates();
1649
1650        return true;
1651    }
1652
1653    @Override
1654    public void disableShortcuts(String packageName, List shortcutIds,
1655            String disabledMessage, int disabledMessageResId, @UserIdInt int userId) {
1656        verifyCaller(packageName, userId);
1657        Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1658
1659        synchronized (mLock) {
1660            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1661
1662            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1663
1664            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1665                ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
1666                        disabledMessage, disabledMessageResId,
1667                        /* overrideImmutable=*/ false);
1668            }
1669
1670            // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
1671            ps.adjustRanks();
1672        }
1673        packageShortcutsChanged(packageName, userId);
1674
1675        verifyStates();
1676    }
1677
1678    @Override
1679    public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) {
1680        verifyCaller(packageName, userId);
1681        Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1682
1683        synchronized (mLock) {
1684            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1685
1686            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1687
1688            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1689                ps.enableWithId((String) shortcutIds.get(i));
1690            }
1691        }
1692        packageShortcutsChanged(packageName, userId);
1693
1694        verifyStates();
1695    }
1696
1697    @Override
1698    public void removeDynamicShortcuts(String packageName, List shortcutIds,
1699            @UserIdInt int userId) {
1700        verifyCaller(packageName, userId);
1701        Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
1702
1703        synchronized (mLock) {
1704            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1705
1706            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
1707
1708            for (int i = shortcutIds.size() - 1; i >= 0; i--) {
1709                ps.deleteDynamicWithId(
1710                        Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
1711            }
1712
1713            // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
1714            ps.adjustRanks();
1715        }
1716        packageShortcutsChanged(packageName, userId);
1717
1718        verifyStates();
1719    }
1720
1721    @Override
1722    public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
1723        verifyCaller(packageName, userId);
1724
1725        synchronized (mLock) {
1726            getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
1727        }
1728        packageShortcutsChanged(packageName, userId);
1729
1730        verifyStates();
1731    }
1732
1733    @Override
1734    public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
1735            @UserIdInt int userId) {
1736        verifyCaller(packageName, userId);
1737        synchronized (mLock) {
1738            return getShortcutsWithQueryLocked(
1739                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1740                    ShortcutInfo::isDynamic);
1741        }
1742    }
1743
1744    @Override
1745    public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
1746            @UserIdInt int userId) {
1747        verifyCaller(packageName, userId);
1748        synchronized (mLock) {
1749            return getShortcutsWithQueryLocked(
1750                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1751                    ShortcutInfo::isManifestShortcut);
1752        }
1753    }
1754
1755    @Override
1756    public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
1757            @UserIdInt int userId) {
1758        verifyCaller(packageName, userId);
1759        synchronized (mLock) {
1760            return getShortcutsWithQueryLocked(
1761                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
1762                    ShortcutInfo::isPinned);
1763        }
1764    }
1765
1766    private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
1767            @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
1768
1769        final ArrayList<ShortcutInfo> ret = new ArrayList<>();
1770
1771        getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags);
1772
1773        return new ParceledListSlice<>(ret);
1774    }
1775
1776    @Override
1777    public int getMaxShortcutCountForActivity(String packageName, @UserIdInt int userId)
1778            throws RemoteException {
1779        verifyCaller(packageName, userId);
1780
1781        return mMaxShortcuts;
1782    }
1783
1784    @Override
1785    public int getRemainingCallCount(String packageName, @UserIdInt int userId) {
1786        verifyCaller(packageName, userId);
1787
1788        synchronized (mLock) {
1789            return mMaxUpdatesPerInterval
1790                    - getPackageShortcutsLocked(packageName, userId).getApiCallCount();
1791        }
1792    }
1793
1794    @Override
1795    public long getRateLimitResetTime(String packageName, @UserIdInt int userId) {
1796        verifyCaller(packageName, userId);
1797
1798        synchronized (mLock) {
1799            return getNextResetTimeLocked();
1800        }
1801    }
1802
1803    @Override
1804    public int getIconMaxDimensions(String packageName, int userId) {
1805        verifyCaller(packageName, userId);
1806
1807        synchronized (mLock) {
1808            return mMaxIconDimension;
1809        }
1810    }
1811
1812    @Override
1813    public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
1814        verifyCaller(packageName, userId);
1815
1816        Preconditions.checkNotNull(shortcutId);
1817
1818        if (DEBUG) {
1819            Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
1820                    shortcutId, packageName, userId));
1821        }
1822
1823        synchronized (mLock) {
1824            final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
1825            if (ps.findShortcutById(shortcutId) == null) {
1826                Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
1827                        packageName, shortcutId));
1828                return;
1829            }
1830        }
1831
1832        final long token = injectClearCallingIdentity();
1833        try {
1834            mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
1835        } finally {
1836            injectRestoreCallingIdentity(token);
1837        }
1838    }
1839
1840    /**
1841     * Reset all throttling, for developer options and command line.  Only system/shell can call
1842     * it.
1843     */
1844    @Override
1845    public void resetThrottling() {
1846        enforceSystemOrShell();
1847
1848        resetThrottlingInner(getCallingUserId());
1849    }
1850
1851    void resetThrottlingInner(@UserIdInt int userId) {
1852        synchronized (mLock) {
1853            getUserShortcutsLocked(userId).resetThrottling();
1854        }
1855        scheduleSaveUser(userId);
1856        Slog.i(TAG, "ShortcutManager: throttling counter reset for user " + userId);
1857    }
1858
1859    void resetAllThrottlingInner() {
1860        synchronized (mLock) {
1861            mRawLastResetTime = injectCurrentTimeMillis();
1862        }
1863        scheduleSaveBaseState();
1864        Slog.i(TAG, "ShortcutManager: throttling counter reset for all users");
1865    }
1866
1867    void resetPackageThrottling(String packageName, int userId) {
1868        synchronized (mLock) {
1869            getPackageShortcutsLocked(packageName, userId)
1870                    .resetRateLimitingForCommandLineNoSaving();
1871            saveUserLocked(userId);
1872        }
1873    }
1874
1875    @Override
1876    public void onApplicationActive(String packageName, int userId) {
1877        if (DEBUG) {
1878            Slog.d(TAG, "onApplicationActive: package=" + packageName + "  userid=" + userId);
1879        }
1880        enforceResetThrottlingPermission();
1881        resetPackageThrottling(packageName, userId);
1882    }
1883
1884    // We override this method in unit tests to do a simpler check.
1885    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
1886        return hasShortcutHostPermissionInner(callingPackage, userId);
1887    }
1888
1889    // This method is extracted so we can directly call this method from unit tests,
1890    // even when hasShortcutPermission() is overridden.
1891    @VisibleForTesting
1892    boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
1893        synchronized (mLock) {
1894            final long start = injectElapsedRealtime();
1895
1896            final ShortcutUser user = getUserShortcutsLocked(userId);
1897
1898            final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
1899
1900            // Default launcher from package manager.
1901            final long startGetHomeActivitiesAsUser = injectElapsedRealtime();
1902            final ComponentName defaultLauncher = injectPackageManagerInternal()
1903                    .getHomeActivitiesAsUser(allHomeCandidates, userId);
1904            logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
1905
1906            ComponentName detected;
1907            if (defaultLauncher != null) {
1908                detected = defaultLauncher;
1909                if (DEBUG) {
1910                    Slog.v(TAG, "Default launcher from PM: " + detected);
1911                }
1912            } else {
1913                detected = user.getDefaultLauncherComponent();
1914
1915                // TODO: Make sure it's still enabled.
1916                if (DEBUG) {
1917                    Slog.v(TAG, "Cached launcher: " + detected);
1918                }
1919            }
1920
1921            if (detected == null) {
1922                // If we reach here, that means it's the first check since the user was created,
1923                // and there's already multiple launchers and there's no default set.
1924                // Find the system one with the highest priority.
1925                // (We need to check the priority too because of FallbackHome in Settings.)
1926                // If there's no system launcher yet, then no one can access shortcuts, until
1927                // the user explicitly
1928                final int size = allHomeCandidates.size();
1929
1930                int lastPriority = Integer.MIN_VALUE;
1931                for (int i = 0; i < size; i++) {
1932                    final ResolveInfo ri = allHomeCandidates.get(i);
1933                    if (!ri.activityInfo.applicationInfo.isSystemApp()) {
1934                        continue;
1935                    }
1936                    if (DEBUG) {
1937                        Slog.d(TAG, String.format("hasShortcutPermissionInner: pkg=%s prio=%d",
1938                                ri.activityInfo.getComponentName(), ri.priority));
1939                    }
1940                    if (ri.priority < lastPriority) {
1941                        continue;
1942                    }
1943                    detected = ri.activityInfo.getComponentName();
1944                    lastPriority = ri.priority;
1945                }
1946            }
1947            logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
1948
1949            if (detected != null) {
1950                if (DEBUG) {
1951                    Slog.v(TAG, "Detected launcher: " + detected);
1952                }
1953                user.setDefaultLauncherComponent(detected);
1954                return detected.getPackageName().equals(callingPackage);
1955            } else {
1956                // Default launcher not found.
1957                return false;
1958            }
1959        }
1960    }
1961
1962    // === House keeping ===
1963
1964    private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
1965            boolean appStillExists) {
1966        synchronized (mLock) {
1967            forEachLoadedUserLocked(user ->
1968                    cleanUpPackageLocked(packageName, user.getUserId(), packageUserId,
1969                            appStillExists));
1970        }
1971    }
1972
1973    /**
1974     * Remove all the information associated with a package.  This will really remove all the
1975     * information, including the restore information (i.e. it'll remove packages even if they're
1976     * shadow).
1977     *
1978     * This is called when an app is uninstalled, or an app gets "clear data"ed.
1979     */
1980    @VisibleForTesting
1981    void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
1982            boolean appStillExists) {
1983        final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
1984
1985        final ShortcutUser user = getUserShortcutsLocked(owningUserId);
1986        boolean doNotify = false;
1987
1988        // First, remove the package from the package list (if the package is a publisher).
1989        if (packageUserId == owningUserId) {
1990            if (user.removePackage(packageName) != null) {
1991                doNotify = true;
1992            }
1993        }
1994
1995        // Also remove from the launcher list (if the package is a launcher).
1996        user.removeLauncher(packageUserId, packageName);
1997
1998        // Then remove pinned shortcuts from all launchers.
1999        user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId));
2000
2001        // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous
2002        // step.  Remove them too.
2003        user.forAllPackages(p -> p.refreshPinnedFlags());
2004
2005        scheduleSaveUser(owningUserId);
2006
2007        if (doNotify) {
2008            notifyListeners(packageName, owningUserId);
2009        }
2010
2011        // If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts.
2012        if (appStillExists && (packageUserId == owningUserId)) {
2013            // This will do the notification and save when needed, so do it after the above
2014            // notifyListeners.
2015            user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ true);
2016        }
2017
2018        if (!wasUserLoaded) {
2019            // Note this will execute the scheduled save.
2020            unloadUserLocked(owningUserId);
2021        }
2022    }
2023
2024    /**
2025     * Entry point from {@link LauncherApps}.
2026     */
2027    private class LocalService extends ShortcutServiceInternal {
2028
2029        @Override
2030        public List<ShortcutInfo> getShortcuts(int launcherUserId,
2031                @NonNull String callingPackage, long changedSince,
2032                @Nullable String packageName, @Nullable List<String> shortcutIds,
2033                @Nullable ComponentName componentName,
2034                int queryFlags, int userId) {
2035            final ArrayList<ShortcutInfo> ret = new ArrayList<>();
2036            final boolean cloneKeyFieldOnly =
2037                    ((queryFlags & ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY) != 0);
2038            final int cloneFlag = cloneKeyFieldOnly ? ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO
2039                    : ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER;
2040            if (packageName == null) {
2041                shortcutIds = null; // LauncherAppsService already threw for it though.
2042            }
2043
2044            synchronized (mLock) {
2045                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2046                        .attemptToRestoreIfNeededAndSave();
2047
2048                if (packageName != null) {
2049                    getShortcutsInnerLocked(launcherUserId,
2050                            callingPackage, packageName, shortcutIds, changedSince,
2051                            componentName, queryFlags, userId, ret, cloneFlag);
2052                } else {
2053                    final List<String> shortcutIdsF = shortcutIds;
2054                    getUserShortcutsLocked(userId).forAllPackages(p -> {
2055                        getShortcutsInnerLocked(launcherUserId,
2056                                callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
2057                                componentName, queryFlags, userId, ret, cloneFlag);
2058                    });
2059                }
2060            }
2061            return ret;
2062        }
2063
2064        private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
2065                @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
2066                @Nullable ComponentName componentName, int queryFlags,
2067                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
2068            final ArraySet<String> ids = shortcutIds == null ? null
2069                    : new ArraySet<>(shortcutIds);
2070
2071            final ShortcutPackage p = getUserShortcutsLocked(userId)
2072                    .getPackageShortcutsIfExists(packageName);
2073            if (p == null) {
2074                return; // No need to instantiate ShortcutPackage.
2075            }
2076
2077            p.findAll(ret,
2078                    (ShortcutInfo si) -> {
2079                        if (si.getLastChangedTimestamp() < changedSince) {
2080                            return false;
2081                        }
2082                        if (ids != null && !ids.contains(si.getId())) {
2083                            return false;
2084                        }
2085                        if (componentName != null) {
2086                            if (si.getActivity() != null
2087                                    && !si.getActivity().equals(componentName)) {
2088                                return false;
2089                            }
2090                        }
2091                        if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
2092                                && si.isDynamic()) {
2093                            return true;
2094                        }
2095                        if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
2096                                && si.isPinned()) {
2097                            return true;
2098                        }
2099                        if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
2100                                && si.isManifestShortcut()) {
2101                            return true;
2102                        }
2103                        return false;
2104                    }, cloneFlag, callingPackage, launcherUserId);
2105        }
2106
2107        @Override
2108        public boolean isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
2109                @NonNull String packageName, @NonNull String shortcutId, int userId) {
2110            Preconditions.checkStringNotEmpty(packageName, "packageName");
2111            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
2112
2113            synchronized (mLock) {
2114                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2115                        .attemptToRestoreIfNeededAndSave();
2116
2117                final ShortcutInfo si = getShortcutInfoLocked(
2118                        launcherUserId, callingPackage, packageName, shortcutId, userId);
2119                return si != null && si.isPinned();
2120            }
2121        }
2122
2123        private ShortcutInfo getShortcutInfoLocked(
2124                int launcherUserId, @NonNull String callingPackage,
2125                @NonNull String packageName, @NonNull String shortcutId, int userId) {
2126            Preconditions.checkStringNotEmpty(packageName, "packageName");
2127            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
2128
2129            final ShortcutPackage p = getUserShortcutsLocked(userId)
2130                    .getPackageShortcutsIfExists(packageName);
2131            if (p == null) {
2132                return null;
2133            }
2134
2135            final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
2136            p.findAll(list,
2137                    (ShortcutInfo si) -> shortcutId.equals(si.getId()),
2138                    /* clone flags=*/ 0, callingPackage, launcherUserId);
2139            return list.size() == 0 ? null : list.get(0);
2140        }
2141
2142        @Override
2143        public void pinShortcuts(int launcherUserId,
2144                @NonNull String callingPackage, @NonNull String packageName,
2145                @NonNull List<String> shortcutIds, int userId) {
2146            // Calling permission must be checked by LauncherAppsImpl.
2147            Preconditions.checkStringNotEmpty(packageName, "packageName");
2148            Preconditions.checkNotNull(shortcutIds, "shortcutIds");
2149
2150            synchronized (mLock) {
2151                final ShortcutLauncher launcher =
2152                        getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
2153                launcher.attemptToRestoreIfNeededAndSave();
2154
2155                launcher.pinShortcuts(userId, packageName, shortcutIds);
2156            }
2157            packageShortcutsChanged(packageName, userId);
2158
2159            verifyStates();
2160        }
2161
2162        @Override
2163        public Intent createShortcutIntent(int launcherUserId,
2164                @NonNull String callingPackage,
2165                @NonNull String packageName, @NonNull String shortcutId, int userId) {
2166            // Calling permission must be checked by LauncherAppsImpl.
2167            Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
2168            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
2169
2170            synchronized (mLock) {
2171                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2172                        .attemptToRestoreIfNeededAndSave();
2173
2174                // Make sure the shortcut is actually visible to the launcher.
2175                final ShortcutInfo si = getShortcutInfoLocked(
2176                        launcherUserId, callingPackage, packageName, shortcutId, userId);
2177                // "si == null" should suffice here, but check the flags too just to make sure.
2178                if (si == null || !si.isEnabled() || !si.isAlive()) {
2179                    return null;
2180                }
2181                return si.getIntent();
2182            }
2183        }
2184
2185        @Override
2186        public void addListener(@NonNull ShortcutChangeListener listener) {
2187            synchronized (mLock) {
2188                mListeners.add(Preconditions.checkNotNull(listener));
2189            }
2190        }
2191
2192        @Override
2193        public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
2194                @NonNull String packageName, @NonNull String shortcutId, int userId) {
2195            Preconditions.checkNotNull(callingPackage, "callingPackage");
2196            Preconditions.checkNotNull(packageName, "packageName");
2197            Preconditions.checkNotNull(shortcutId, "shortcutId");
2198
2199            synchronized (mLock) {
2200                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2201                        .attemptToRestoreIfNeededAndSave();
2202
2203                final ShortcutPackage p = getUserShortcutsLocked(userId)
2204                        .getPackageShortcutsIfExists(packageName);
2205                if (p == null) {
2206                    return 0;
2207                }
2208
2209                final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
2210                return (shortcutInfo != null && shortcutInfo.hasIconResource())
2211                        ? shortcutInfo.getIconResourceId() : 0;
2212            }
2213        }
2214
2215        @Override
2216        public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
2217                @NonNull String callingPackage, @NonNull String packageName,
2218                @NonNull String shortcutId, int userId) {
2219            Preconditions.checkNotNull(callingPackage, "callingPackage");
2220            Preconditions.checkNotNull(packageName, "packageName");
2221            Preconditions.checkNotNull(shortcutId, "shortcutId");
2222
2223            synchronized (mLock) {
2224                getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
2225                        .attemptToRestoreIfNeededAndSave();
2226
2227                final ShortcutPackage p = getUserShortcutsLocked(userId)
2228                        .getPackageShortcutsIfExists(packageName);
2229                if (p == null) {
2230                    return null;
2231                }
2232
2233                final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
2234                if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
2235                    return null;
2236                }
2237                try {
2238                    if (shortcutInfo.getBitmapPath() == null) {
2239                        Slog.w(TAG, "null bitmap detected in getShortcutIconFd()");
2240                        return null;
2241                    }
2242                    return ParcelFileDescriptor.open(
2243                            new File(shortcutInfo.getBitmapPath()),
2244                            ParcelFileDescriptor.MODE_READ_ONLY);
2245                } catch (FileNotFoundException e) {
2246                    Slog.e(TAG, "Icon file not found: " + shortcutInfo.getBitmapPath());
2247                    return null;
2248                }
2249            }
2250        }
2251
2252        @Override
2253        public boolean hasShortcutHostPermission(int launcherUserId,
2254                @NonNull String callingPackage) {
2255            return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
2256        }
2257
2258        /**
2259         * Called by AM when the system locale changes *within the AM lock.  ABSOLUTELY do not take
2260         * any locks in this method.
2261         */
2262        @Override
2263        public void onSystemLocaleChangedNoLock() {
2264            // DO NOT HOLD ANY LOCKS HERE.
2265
2266            // We want to reset throttling for all packages for all users.  But we can't just do so
2267            // here because:
2268            // - We can't load/save users that are locked.
2269            // - Even for loaded users, resetting the counters would require us to hold mLock.
2270            //
2271            // So we use a "pull" model instead.  In here, we just increment the "locale change
2272            // sequence number".  Each ShortcutUser has the "last known locale change sequence".
2273            //
2274            // This allows ShortcutUser's to detect the system locale change, so they can reset
2275            // counters.
2276
2277            // Ignore all callback during system boot.
2278            if (mBootCompleted.get()) {
2279                mLocaleChangeSequenceNumber.incrementAndGet();
2280                if (DEBUG) {
2281                    Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get());
2282                }
2283                injectPostToHandler(() -> handleLocaleChanged());
2284            }
2285        }
2286    }
2287
2288    void handleLocaleChanged() {
2289        if (DEBUG) {
2290            Slog.d(TAG, "handleLocaleChanged");
2291        }
2292        scheduleSaveBaseState();
2293
2294        final long token = injectClearCallingIdentity();
2295        try {
2296            forEachLoadedUserLocked(u -> u.forAllPackages(p -> p.resolveResourceStrings()));
2297        } finally {
2298            injectRestoreCallingIdentity(token);
2299        }
2300    }
2301
2302    /**
2303     * Package event callbacks.
2304     */
2305    @VisibleForTesting
2306    final PackageMonitor mPackageMonitor = new PackageMonitor() {
2307        @Override
2308        public void onPackageAdded(String packageName, int uid) {
2309            handlePackageAdded(packageName, getChangingUserId());
2310        }
2311
2312        @Override
2313        public void onPackageUpdateFinished(String packageName, int uid) {
2314            handlePackageUpdateFinished(packageName, getChangingUserId());
2315        }
2316
2317        @Override
2318        public void onPackageRemoved(String packageName, int uid) {
2319            handlePackageRemoved(packageName, getChangingUserId());
2320        }
2321
2322        @Override
2323        public void onPackageDataCleared(String packageName, int uid) {
2324            handlePackageDataCleared(packageName, getChangingUserId());
2325        }
2326
2327        @Override
2328        public boolean onPackageChanged(String packageName, int uid, String[] components) {
2329            handlePackageChanged(packageName, getChangingUserId());
2330            return false; // We don't need to receive onSomePackagesChanged(), so just false.
2331        }
2332    };
2333
2334    /**
2335     * Called when a user is unlocked.
2336     * - Check all known packages still exist, and otherwise perform cleanup.
2337     * - If a package still exists, check the version code.  If it's been updated, may need to
2338     * update timestamps of its shortcuts.
2339     */
2340    @VisibleForTesting
2341    void checkPackageChanges(@UserIdInt int ownerUserId) {
2342        if (DEBUG) {
2343            Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
2344        }
2345        if (injectIsSafeModeEnabled()) {
2346            Slog.i(TAG, "Safe mode, skipping checkPackageChanges()");
2347            return;
2348        }
2349
2350        final long start = injectElapsedRealtime();
2351        try {
2352            final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
2353
2354            synchronized (mLock) {
2355                final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
2356
2357                // Find packages that have been uninstalled.
2358                user.forAllPackageItems(spi -> {
2359                    if (spi.getPackageInfo().isShadow()) {
2360                        return; // Don't delete shadow information.
2361                    }
2362                    if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
2363                        gonePackages.add(PackageWithUser.of(spi));
2364                    }
2365                });
2366                if (gonePackages.size() > 0) {
2367                    for (int i = gonePackages.size() - 1; i >= 0; i--) {
2368                        final PackageWithUser pu = gonePackages.get(i);
2369                        cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId,
2370                                /* appStillExists = */ false);
2371                    }
2372                }
2373                final long now = injectCurrentTimeMillis();
2374
2375                // Then for each installed app, publish manifest shortcuts when needed.
2376                forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
2377                    user.handlePackageAddedOrUpdated(ai.packageName, /* forceRescan=*/ false);
2378                });
2379
2380                // Write the time just before the scan, because there may be apps that have just
2381                // been updated, and we want to catch them in the next time.
2382                user.setLastAppScanTime(now);
2383                scheduleSaveUser(ownerUserId);
2384            }
2385        } finally {
2386            logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
2387        }
2388        verifyStates();
2389    }
2390
2391    private void handlePackageAdded(String packageName, @UserIdInt int userId) {
2392        if (DEBUG) {
2393            Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
2394        }
2395        synchronized (mLock) {
2396            final ShortcutUser user = getUserShortcutsLocked(userId);
2397            user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
2398            user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
2399        }
2400        verifyStates();
2401    }
2402
2403    private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
2404        if (DEBUG) {
2405            Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
2406                    packageName, userId));
2407        }
2408        synchronized (mLock) {
2409            final ShortcutUser user = getUserShortcutsLocked(userId);
2410            user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
2411
2412            if (isPackageInstalled(packageName, userId)) {
2413                user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
2414            }
2415        }
2416        verifyStates();
2417    }
2418
2419    private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
2420        if (DEBUG) {
2421            Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
2422                    packageUserId));
2423        }
2424        cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false);
2425
2426        verifyStates();
2427    }
2428
2429    private void handlePackageDataCleared(String packageName, int packageUserId) {
2430        if (DEBUG) {
2431            Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
2432                    packageUserId));
2433        }
2434        cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true);
2435
2436        verifyStates();
2437    }
2438
2439    private void handlePackageChanged(String packageName, int packageUserId) {
2440        if (DEBUG) {
2441            Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName,
2442                    packageUserId));
2443        }
2444
2445        // Activities may be disabled or enabled.  Just rescan the package.
2446        synchronized (mLock) {
2447            final ShortcutUser user = getUserShortcutsLocked(packageUserId);
2448
2449            user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ true);
2450        }
2451
2452        verifyStates();
2453    }
2454
2455    // === PackageManager interaction ===
2456
2457    @Nullable
2458    PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
2459        return injectPackageInfo(packageName, userId, true);
2460    }
2461
2462    @Nullable
2463    PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
2464        return injectPackageInfo(packageName, userId, false);
2465    }
2466
2467    int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
2468        final long token = injectClearCallingIdentity();
2469        try {
2470            return mIPackageManager.getPackageUid(packageName, PACKAGE_MATCH_FLAGS
2471                    , userId);
2472        } catch (RemoteException e) {
2473            // Shouldn't happen.
2474            Slog.wtf(TAG, "RemoteException", e);
2475            return -1;
2476        } finally {
2477            injectRestoreCallingIdentity(token);
2478        }
2479    }
2480
2481    @Nullable
2482    @VisibleForTesting
2483    PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
2484            boolean getSignatures) {
2485        final long start = injectElapsedRealtime();
2486        final long token = injectClearCallingIdentity();
2487        try {
2488            return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
2489                            | (getSignatures ? PackageManager.GET_SIGNATURES : 0)
2490                    , userId);
2491        } catch (RemoteException e) {
2492            // Shouldn't happen.
2493            Slog.wtf(TAG, "RemoteException", e);
2494            return null;
2495        } finally {
2496            injectRestoreCallingIdentity(token);
2497
2498            logDurationStat(
2499                    (getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
2500                    start);
2501        }
2502    }
2503
2504    @Nullable
2505    @VisibleForTesting
2506    ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
2507        final long start = injectElapsedRealtime();
2508        final long token = injectClearCallingIdentity();
2509        try {
2510            return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
2511        } catch (RemoteException e) {
2512            // Shouldn't happen.
2513            Slog.wtf(TAG, "RemoteException", e);
2514            return null;
2515        } finally {
2516            injectRestoreCallingIdentity(token);
2517
2518            logDurationStat(Stats.GET_APPLICATION_INFO, start);
2519        }
2520    }
2521
2522    @Nullable
2523    ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) {
2524        final long start = injectElapsedRealtime();
2525        final long token = injectClearCallingIdentity();
2526        try {
2527            return mIPackageManager.getActivityInfo(activity,
2528                    PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId);
2529        } catch (RemoteException e) {
2530            // Shouldn't happen.
2531            Slog.wtf(TAG, "RemoteException", e);
2532            return null;
2533        } finally {
2534            injectRestoreCallingIdentity(token);
2535
2536            logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start);
2537        }
2538    }
2539
2540    @Nullable
2541    @VisibleForTesting
2542    List<PackageInfo> injectInstalledPackages(@UserIdInt int userId) {
2543        final long start = injectElapsedRealtime();
2544        final long token = injectClearCallingIdentity();
2545        try {
2546            final ParceledListSlice<PackageInfo> parceledList =
2547                    mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId);
2548            if (parceledList == null) {
2549                return Collections.emptyList();
2550            }
2551            return parceledList.getList();
2552        } catch (RemoteException e) {
2553            // Shouldn't happen.
2554            Slog.wtf(TAG, "RemoteException", e);
2555            return null;
2556        } finally {
2557            injectRestoreCallingIdentity(token);
2558
2559            logDurationStat(Stats.GET_INSTALLED_PACKAGES, start);
2560        }
2561    }
2562
2563    private void forUpdatedPackages(@UserIdInt int userId, long lastScanTime,
2564            Consumer<ApplicationInfo> callback) {
2565        if (DEBUG) {
2566            Slog.d(TAG, "forUpdatedPackages for user " + userId + ", lastScanTime=" + lastScanTime);
2567        }
2568        final List<PackageInfo> list = injectInstalledPackages(userId);
2569        for (int i = list.size() - 1; i >= 0; i--) {
2570            final PackageInfo pi = list.get(i);
2571
2572            if (((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
2573                    && (pi.lastUpdateTime >= lastScanTime)) {
2574                if (DEBUG) {
2575                    Slog.d(TAG, "Found updated package " + pi.packageName);
2576                }
2577                callback.accept(pi.applicationInfo);
2578            }
2579        }
2580    }
2581
2582    private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
2583        final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
2584        return (ai != null) && ((ai.flags & flags) == flags);
2585    }
2586
2587    boolean isPackageInstalled(String packageName, int userId) {
2588        return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
2589    }
2590
2591    @Nullable
2592    XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
2593        return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
2594    }
2595
2596    @Nullable
2597    Resources injectGetResourcesForApplicationAsUser(String packageName, int userId) {
2598        final long start = injectElapsedRealtime();
2599        final long token = injectClearCallingIdentity();
2600        try {
2601            return mContext.getPackageManager().getResourcesForApplicationAsUser(
2602                    packageName, userId);
2603        } catch (NameNotFoundException e) {
2604            Slog.e(TAG, "Resources for package " + packageName + " not found");
2605            return null;
2606        } finally {
2607            injectRestoreCallingIdentity(token);
2608
2609            logDurationStat(Stats.GET_APPLICATION_RESOURCES, start);
2610        }
2611    }
2612
2613    private Intent getMainActivityIntent() {
2614        final Intent intent = new Intent(Intent.ACTION_MAIN);
2615        intent.addCategory(LAUNCHER_INTENT_CATEGORY);
2616        return intent;
2617    }
2618
2619    @Nullable
2620    ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) {
2621        final long start = injectElapsedRealtime();
2622        final long token = injectClearCallingIdentity();
2623        try {
2624            final Intent intent = getMainActivityIntent();
2625            intent.setPackage(packageName);
2626
2627            final List<ResolveInfo> resolved =
2628                    mContext.getPackageManager().queryIntentActivitiesAsUser(
2629                            intent, PACKAGE_MATCH_FLAGS, userId);
2630
2631            return (resolved == null || resolved.size() == 0)
2632                    ? null : resolved.get(0).activityInfo.getComponentName();
2633        } finally {
2634            injectRestoreCallingIdentity(token);
2635
2636            logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start);
2637        }
2638    }
2639
2640    boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
2641        final long start = injectElapsedRealtime();
2642        final long token = injectClearCallingIdentity();
2643        try {
2644            final Intent intent = getMainActivityIntent();
2645            intent.setPackage(activity.getPackageName());
2646            intent.setComponent(activity);
2647
2648            final List<ResolveInfo> resolved =
2649                    mContext.getPackageManager().queryIntentActivitiesAsUser(
2650                            intent, PACKAGE_MATCH_FLAGS, userId);
2651
2652            return resolved != null && resolved.size() > 0;
2653        } finally {
2654            injectRestoreCallingIdentity(token);
2655
2656            logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
2657        }
2658    }
2659
2660    @NonNull
2661    List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) {
2662        final long start = injectElapsedRealtime();
2663        final long token = injectClearCallingIdentity();
2664        try {
2665            final Intent intent = getMainActivityIntent();
2666            intent.setPackage(packageName);
2667
2668            final List<ResolveInfo> resolved =
2669                    mContext.getPackageManager().queryIntentActivitiesAsUser(
2670                            intent, PACKAGE_MATCH_FLAGS, userId);
2671
2672            return (resolved != null) ? resolved : new ArrayList<>(0);
2673        } finally {
2674            injectRestoreCallingIdentity(token);
2675
2676            logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
2677        }
2678    }
2679
2680    boolean injectIsSafeModeEnabled() {
2681        final long token = injectClearCallingIdentity();
2682        try {
2683            return IWindowManager.Stub
2684                    .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE))
2685                    .isSafeModeEnabled();
2686        } catch (RemoteException e) {
2687            return false; // Shouldn't happen though.
2688        } finally {
2689            injectRestoreCallingIdentity(token);
2690        }
2691    }
2692
2693    // === Backup & restore ===
2694
2695    boolean shouldBackupApp(String packageName, int userId) {
2696        return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
2697    }
2698
2699    boolean shouldBackupApp(PackageInfo pi) {
2700        return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
2701    }
2702
2703    @Override
2704    public byte[] getBackupPayload(@UserIdInt int userId) {
2705        enforceSystem();
2706        if (DEBUG) {
2707            Slog.d(TAG, "Backing up user " + userId);
2708        }
2709        synchronized (mLock) {
2710            final ShortcutUser user = getUserShortcutsLocked(userId);
2711            if (user == null) {
2712                Slog.w(TAG, "Can't backup: user not found: id=" + userId);
2713                return null;
2714            }
2715
2716            user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave());
2717
2718            // Then save.
2719            final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
2720            try {
2721                saveUserInternalLocked(userId, os, /* forBackup */ true);
2722            } catch (XmlPullParserException | IOException e) {
2723                // Shouldn't happen.
2724                Slog.w(TAG, "Backup failed.", e);
2725                return null;
2726            }
2727            return os.toByteArray();
2728        }
2729    }
2730
2731    @Override
2732    public void applyRestore(byte[] payload, @UserIdInt int userId) {
2733        enforceSystem();
2734        if (DEBUG) {
2735            Slog.d(TAG, "Restoring user " + userId);
2736        }
2737        final ShortcutUser user;
2738        final ByteArrayInputStream is = new ByteArrayInputStream(payload);
2739        try {
2740            user = loadUserInternal(userId, is, /* fromBackup */ true);
2741        } catch (XmlPullParserException | IOException e) {
2742            Slog.w(TAG, "Restoration failed.", e);
2743            return;
2744        }
2745        synchronized (mLock) {
2746            mUsers.put(userId, user);
2747
2748            // Then purge all the save images.
2749            final File bitmapPath = getUserBitmapFilePath(userId);
2750            final boolean success = FileUtils.deleteContents(bitmapPath);
2751            if (!success) {
2752                Slog.w(TAG, "Failed to delete " + bitmapPath);
2753            }
2754
2755            saveUserLocked(userId);
2756        }
2757    }
2758
2759    // === Dump ===
2760
2761    @Override
2762    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2763        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2764                != PackageManager.PERMISSION_GRANTED) {
2765            pw.println("Permission Denial: can't dump UserManager from from pid="
2766                    + Binder.getCallingPid()
2767                    + ", uid=" + Binder.getCallingUid()
2768                    + " without permission "
2769                    + android.Manifest.permission.DUMP);
2770            return;
2771        }
2772        dumpInner(pw, args);
2773    }
2774
2775    @VisibleForTesting
2776    void dumpInner(PrintWriter pw, String[] args) {
2777        synchronized (mLock) {
2778            final long now = injectCurrentTimeMillis();
2779            pw.print("Now: [");
2780            pw.print(now);
2781            pw.print("] ");
2782            pw.print(formatTime(now));
2783
2784            pw.print("  Raw last reset: [");
2785            pw.print(mRawLastResetTime);
2786            pw.print("] ");
2787            pw.print(formatTime(mRawLastResetTime));
2788
2789            final long last = getLastResetTimeLocked();
2790            pw.print("  Last reset: [");
2791            pw.print(last);
2792            pw.print("] ");
2793            pw.print(formatTime(last));
2794
2795            final long next = getNextResetTimeLocked();
2796            pw.print("  Next reset: [");
2797            pw.print(next);
2798            pw.print("] ");
2799            pw.print(formatTime(next));
2800
2801            pw.print("  Locale change seq#: ");
2802            pw.print(mLocaleChangeSequenceNumber.get());
2803            pw.println();
2804
2805            pw.print("  Config:");
2806            pw.print("    Max icon dim: ");
2807            pw.println(mMaxIconDimension);
2808            pw.print("    Icon format: ");
2809            pw.println(mIconPersistFormat);
2810            pw.print("    Icon quality: ");
2811            pw.println(mIconPersistQuality);
2812            pw.print("    saveDelayMillis: ");
2813            pw.println(mSaveDelayMillis);
2814            pw.print("    resetInterval: ");
2815            pw.println(mResetInterval);
2816            pw.print("    maxUpdatesPerInterval: ");
2817            pw.println(mMaxUpdatesPerInterval);
2818            pw.print("    maxShortcutsPerActivity: ");
2819            pw.println(mMaxShortcuts);
2820            pw.println();
2821
2822            pw.println("  Stats:");
2823            synchronized (mStatLock) {
2824                final String p = "    ";
2825                dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()");
2826                dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check");
2827
2828                dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
2829                dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
2830                dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
2831                dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
2832                dumpStatLS(pw, p, Stats.GET_ACTIVITY_WITH_METADATA, "getActivity+metadata");
2833                dumpStatLS(pw, p, Stats.GET_INSTALLED_PACKAGES, "getInstalledPackages");
2834                dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
2835                dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources");
2836                dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup");
2837                dumpStatLS(pw, p, Stats.GET_LAUNCHER_ACTIVITY, "getLauncherActivity");
2838                dumpStatLS(pw, p, Stats.CHECK_LAUNCHER_ACTIVITY, "checkLauncherActivity");
2839            }
2840
2841            for (int i = 0; i < mUsers.size(); i++) {
2842                pw.println();
2843                mUsers.valueAt(i).dump(pw, "  ");
2844            }
2845
2846            pw.println();
2847            pw.println("  UID state:");
2848
2849            for (int i = 0; i < mUidState.size(); i++) {
2850                final int uid = mUidState.keyAt(i);
2851                final int state = mUidState.valueAt(i);
2852                pw.print("    UID=");
2853                pw.print(uid);
2854                pw.print(" state=");
2855                pw.print(state);
2856                if (isProcessStateForeground(state)) {
2857                    pw.print("  [FG]");
2858                }
2859                pw.print("  last FG=");
2860                pw.print(mUidLastForegroundElapsedTime.get(uid));
2861                pw.println();
2862            }
2863        }
2864    }
2865
2866    static String formatTime(long time) {
2867        Time tobj = new Time();
2868        tobj.set(time);
2869        return tobj.format("%Y-%m-%d %H:%M:%S");
2870    }
2871
2872    private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) {
2873        pw.print(prefix);
2874        final int count = mCountStats[statId];
2875        final long dur = mDurationStats[statId];
2876        pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
2877                label, count, dur,
2878                (count == 0 ? 0 : ((double) dur) / count)));
2879    }
2880
2881    // === Shell support ===
2882
2883    @Override
2884    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
2885            String[] args, ResultReceiver resultReceiver) throws RemoteException {
2886
2887        enforceShell();
2888
2889        final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
2890
2891        resultReceiver.send(status, null);
2892    }
2893
2894    static class CommandException extends Exception {
2895        public CommandException(String message) {
2896            super(message);
2897        }
2898    }
2899
2900    /**
2901     * Handle "adb shell cmd".
2902     */
2903    private class MyShellCommand extends ShellCommand {
2904
2905        private int mUserId = UserHandle.USER_SYSTEM;
2906
2907        private void parseOptions(boolean takeUser)
2908                throws CommandException {
2909            String opt;
2910            while ((opt = getNextOption()) != null) {
2911                switch (opt) {
2912                    case "--user":
2913                        if (takeUser) {
2914                            mUserId = UserHandle.parseUserArg(getNextArgRequired());
2915                            break;
2916                        }
2917                        // fallthrough
2918                    default:
2919                        throw new CommandException("Unknown option: " + opt);
2920                }
2921            }
2922        }
2923
2924        @Override
2925        public int onCommand(String cmd) {
2926            if (cmd == null) {
2927                return handleDefaultCommands(cmd);
2928            }
2929            final PrintWriter pw = getOutPrintWriter();
2930            try {
2931                switch (cmd) {
2932                    case "reset-package-throttling":
2933                        handleResetPackageThrottling();
2934                        break;
2935                    case "reset-throttling":
2936                        handleResetThrottling();
2937                        break;
2938                    case "reset-all-throttling":
2939                        handleResetAllThrottling();
2940                        break;
2941                    case "override-config":
2942                        handleOverrideConfig();
2943                        break;
2944                    case "reset-config":
2945                        handleResetConfig();
2946                        break;
2947                    case "clear-default-launcher":
2948                        handleClearDefaultLauncher();
2949                        break;
2950                    case "get-default-launcher":
2951                        handleGetDefaultLauncher();
2952                        break;
2953                    case "refresh-default-launcher":
2954                        handleRefreshDefaultLauncher();
2955                        break;
2956                    case "unload-user":
2957                        handleUnloadUser();
2958                        break;
2959                    case "clear-shortcuts":
2960                        handleClearShortcuts();
2961                        break;
2962                    case "verify-states": // hidden command to verify various internal states.
2963                        handleVerifyStates();
2964                        break;
2965                    default:
2966                        return handleDefaultCommands(cmd);
2967                }
2968            } catch (CommandException e) {
2969                pw.println("Error: " + e.getMessage());
2970                return 1;
2971            }
2972            pw.println("Success");
2973            return 0;
2974        }
2975
2976        @Override
2977        public void onHelp() {
2978            final PrintWriter pw = getOutPrintWriter();
2979            pw.println("Usage: cmd shortcut COMMAND [options ...]");
2980            pw.println();
2981            pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
2982            pw.println("    Reset throttling for a package");
2983            pw.println();
2984            pw.println("cmd shortcut reset-throttling [--user USER_ID]");
2985            pw.println("    Reset throttling for all packages and users");
2986            pw.println();
2987            pw.println("cmd shortcut reset-all-throttling");
2988            pw.println("    Reset the throttling state for all users");
2989            pw.println();
2990            pw.println("cmd shortcut override-config CONFIG");
2991            pw.println("    Override the configuration for testing (will last until reboot)");
2992            pw.println();
2993            pw.println("cmd shortcut reset-config");
2994            pw.println("    Reset the configuration set with \"update-config\"");
2995            pw.println();
2996            pw.println("cmd shortcut clear-default-launcher [--user USER_ID]");
2997            pw.println("    Clear the cached default launcher");
2998            pw.println();
2999            pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
3000            pw.println("    Show the cached default launcher");
3001            pw.println();
3002            pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
3003            pw.println("    Refresh the cached default launcher");
3004            pw.println();
3005            pw.println("cmd shortcut unload-user [--user USER_ID]");
3006            pw.println("    Unload a user from the memory");
3007            pw.println("    (This should not affect any observable behavior)");
3008            pw.println();
3009            pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE");
3010            pw.println("    Remove all shortcuts from a package, including pinned shortcuts");
3011            pw.println();
3012        }
3013
3014        private void handleResetThrottling() throws CommandException {
3015            parseOptions(/* takeUser =*/ true);
3016
3017            Slog.i(TAG, "cmd: handleResetThrottling");
3018
3019            resetThrottlingInner(mUserId);
3020        }
3021
3022        private void handleResetAllThrottling() {
3023            Slog.i(TAG, "cmd: handleResetAllThrottling");
3024
3025            resetAllThrottlingInner();
3026        }
3027
3028        private void handleResetPackageThrottling() throws CommandException {
3029            parseOptions(/* takeUser =*/ true);
3030
3031            final String packageName = getNextArgRequired();
3032
3033            Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName);
3034
3035            resetPackageThrottling(packageName, mUserId);
3036        }
3037
3038        private void handleOverrideConfig() throws CommandException {
3039            final String config = getNextArgRequired();
3040
3041            Slog.i(TAG, "cmd: handleOverrideConfig: " + config);
3042
3043            synchronized (mLock) {
3044                if (!updateConfigurationLocked(config)) {
3045                    throw new CommandException("override-config failed.  See logcat for details.");
3046                }
3047            }
3048        }
3049
3050        private void handleResetConfig() {
3051            Slog.i(TAG, "cmd: handleResetConfig");
3052
3053            synchronized (mLock) {
3054                loadConfigurationLocked();
3055            }
3056        }
3057
3058        private void clearLauncher() {
3059            synchronized (mLock) {
3060                getUserShortcutsLocked(mUserId).setDefaultLauncherComponent(null);
3061            }
3062        }
3063
3064        private void showLauncher() {
3065            synchronized (mLock) {
3066                // This ensures to set the cached launcher.  Package name doesn't matter.
3067                hasShortcutHostPermissionInner("-", mUserId);
3068
3069                getOutPrintWriter().println("Launcher: "
3070                        + getUserShortcutsLocked(mUserId).getDefaultLauncherComponent());
3071            }
3072        }
3073
3074        private void handleClearDefaultLauncher() throws CommandException {
3075            parseOptions(/* takeUser =*/ true);
3076
3077            clearLauncher();
3078        }
3079
3080        private void handleGetDefaultLauncher() throws CommandException {
3081            parseOptions(/* takeUser =*/ true);
3082
3083            showLauncher();
3084        }
3085
3086        private void handleRefreshDefaultLauncher() throws CommandException {
3087            parseOptions(/* takeUser =*/ true);
3088
3089            clearLauncher();
3090            showLauncher();
3091        }
3092
3093        private void handleUnloadUser() throws CommandException {
3094            parseOptions(/* takeUser =*/ true);
3095
3096            Slog.i(TAG, "cmd: handleUnloadUser: " + mUserId);
3097
3098            ShortcutService.this.handleCleanupUser(mUserId);
3099        }
3100
3101        private void handleClearShortcuts() throws CommandException {
3102            parseOptions(/* takeUser =*/ true);
3103            final String packageName = getNextArgRequired();
3104
3105            Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName);
3106
3107            ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId,
3108                    /* appStillExists = */ true);
3109        }
3110
3111        private void handleVerifyStates() throws CommandException {
3112            try {
3113                verifyStatesForce(); // This will throw when there's an issue.
3114            } catch (Throwable th) {
3115                throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th));
3116            }
3117        }
3118    }
3119
3120    // === Unit test support ===
3121
3122    // Injection point.
3123    @VisibleForTesting
3124    long injectCurrentTimeMillis() {
3125        return System.currentTimeMillis();
3126    }
3127
3128    @VisibleForTesting
3129    long injectElapsedRealtime() {
3130        return SystemClock.elapsedRealtime();
3131    }
3132
3133    // Injection point.
3134    @VisibleForTesting
3135    int injectBinderCallingUid() {
3136        return getCallingUid();
3137    }
3138
3139    private int getCallingUserId() {
3140        return UserHandle.getUserId(injectBinderCallingUid());
3141    }
3142
3143    // Injection point.
3144    @VisibleForTesting
3145    long injectClearCallingIdentity() {
3146        return Binder.clearCallingIdentity();
3147    }
3148
3149    // Injection point.
3150    @VisibleForTesting
3151    void injectRestoreCallingIdentity(long token) {
3152        Binder.restoreCallingIdentity(token);
3153    }
3154
3155    final void wtf(String message) {
3156        wtf(message, /* exception= */ null);
3157    }
3158
3159    // Injection point.
3160    void wtf(String message, Exception e) {
3161        Slog.wtf(TAG, message, e);
3162    }
3163
3164    @VisibleForTesting
3165    File injectSystemDataPath() {
3166        return Environment.getDataSystemDirectory();
3167    }
3168
3169    @VisibleForTesting
3170    File injectUserDataPath(@UserIdInt int userId) {
3171        return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER);
3172    }
3173
3174    @VisibleForTesting
3175    boolean injectIsLowRamDevice() {
3176        return ActivityManager.isLowRamDeviceStatic();
3177    }
3178
3179    @VisibleForTesting
3180    void injectRegisterUidObserver(IUidObserver observer, int which) {
3181        try {
3182            ActivityManagerNative.getDefault().registerUidObserver(observer, which);
3183        } catch (RemoteException shouldntHappen) {
3184        }
3185    }
3186
3187    @VisibleForTesting
3188    PackageManagerInternal injectPackageManagerInternal() {
3189        return mPackageManagerInternal;
3190    }
3191
3192    File getUserBitmapFilePath(@UserIdInt int userId) {
3193        return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS);
3194    }
3195
3196    @VisibleForTesting
3197    SparseArray<ShortcutUser> getShortcutsForTest() {
3198        return mUsers;
3199    }
3200
3201    @VisibleForTesting
3202    int getMaxShortcutsForTest() {
3203        return mMaxShortcuts;
3204    }
3205
3206    @VisibleForTesting
3207    int getMaxUpdatesPerIntervalForTest() {
3208        return mMaxUpdatesPerInterval;
3209    }
3210
3211    @VisibleForTesting
3212    long getResetIntervalForTest() {
3213        return mResetInterval;
3214    }
3215
3216    @VisibleForTesting
3217    int getMaxIconDimensionForTest() {
3218        return mMaxIconDimension;
3219    }
3220
3221    @VisibleForTesting
3222    CompressFormat getIconPersistFormatForTest() {
3223        return mIconPersistFormat;
3224    }
3225
3226    @VisibleForTesting
3227    int getIconPersistQualityForTest() {
3228        return mIconPersistQuality;
3229    }
3230
3231    @VisibleForTesting
3232    ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
3233        synchronized (mLock) {
3234            final ShortcutUser user = mUsers.get(userId);
3235            if (user == null) return null;
3236
3237            return user.getAllPackagesForTest().get(packageName);
3238        }
3239    }
3240
3241    @VisibleForTesting
3242    ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
3243        synchronized (mLock) {
3244            final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
3245            if (pkg == null) return null;
3246
3247            return pkg.findShortcutById(shortcutId);
3248        }
3249    }
3250
3251    /**
3252     * Control whether {@link #verifyStates} should be performed.  We always perform it during unit
3253     * tests.
3254     */
3255    @VisibleForTesting
3256    boolean injectShouldPerformVerification() {
3257        return DEBUG;
3258    }
3259
3260    /**
3261     * Check various internal states and throws if there's any inconsistency.
3262     * This is normally only enabled during unit tests.
3263     */
3264    final void verifyStates() {
3265        if (injectShouldPerformVerification()) {
3266            verifyStatesInner();
3267        }
3268    }
3269
3270    private final void verifyStatesForce() {
3271        verifyStatesInner();
3272    }
3273
3274    private void verifyStatesInner() {
3275        synchronized (this) {
3276            forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
3277        }
3278    }
3279}
3280