Home | History | Annotate | Download | only in pm
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.server.pm;
     17 
     18 import android.annotation.NonNull;
     19 import android.annotation.Nullable;
     20 import android.annotation.UserIdInt;
     21 import android.content.ComponentName;
     22 import android.content.Intent;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.ShortcutInfo;
     25 import android.content.res.Resources;
     26 import android.os.PersistableBundle;
     27 import android.text.format.Formatter;
     28 import android.util.ArrayMap;
     29 import android.util.ArraySet;
     30 import android.util.Log;
     31 import android.util.Slog;
     32 
     33 import com.android.internal.annotations.VisibleForTesting;
     34 import com.android.internal.util.Preconditions;
     35 import com.android.internal.util.XmlUtils;
     36 import com.android.server.pm.ShortcutService.DumpFilter;
     37 import com.android.server.pm.ShortcutService.ShortcutOperation;
     38 import com.android.server.pm.ShortcutService.Stats;
     39 
     40 import org.json.JSONException;
     41 import org.json.JSONObject;
     42 import org.xmlpull.v1.XmlPullParser;
     43 import org.xmlpull.v1.XmlPullParserException;
     44 import org.xmlpull.v1.XmlSerializer;
     45 
     46 import java.io.File;
     47 import java.io.IOException;
     48 import java.io.PrintWriter;
     49 import java.util.ArrayList;
     50 import java.util.Collections;
     51 import java.util.Comparator;
     52 import java.util.List;
     53 import java.util.Set;
     54 import java.util.function.Predicate;
     55 
     56 /**
     57  * Package information used by {@link ShortcutService}.
     58  * User information used by {@link ShortcutService}.
     59  *
     60  * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
     61  */
     62 class ShortcutPackage extends ShortcutPackageItem {
     63     private static final String TAG = ShortcutService.TAG;
     64     private static final String TAG_VERIFY = ShortcutService.TAG + ".verify";
     65 
     66     static final String TAG_ROOT = "package";
     67     private static final String TAG_INTENT_EXTRAS_LEGACY = "intent-extras";
     68     private static final String TAG_INTENT = "intent";
     69     private static final String TAG_EXTRAS = "extras";
     70     private static final String TAG_SHORTCUT = "shortcut";
     71     private static final String TAG_CATEGORIES = "categories";
     72 
     73     private static final String ATTR_NAME = "name";
     74     private static final String ATTR_CALL_COUNT = "call-count";
     75     private static final String ATTR_LAST_RESET = "last-reset";
     76     private static final String ATTR_ID = "id";
     77     private static final String ATTR_ACTIVITY = "activity";
     78     private static final String ATTR_TITLE = "title";
     79     private static final String ATTR_TITLE_RES_ID = "titleid";
     80     private static final String ATTR_TITLE_RES_NAME = "titlename";
     81     private static final String ATTR_TEXT = "text";
     82     private static final String ATTR_TEXT_RES_ID = "textid";
     83     private static final String ATTR_TEXT_RES_NAME = "textname";
     84     private static final String ATTR_DISABLED_MESSAGE = "dmessage";
     85     private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
     86     private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename";
     87     private static final String ATTR_DISABLED_REASON = "disabled-reason";
     88     private static final String ATTR_INTENT_LEGACY = "intent";
     89     private static final String ATTR_INTENT_NO_EXTRA = "intent-base";
     90     private static final String ATTR_RANK = "rank";
     91     private static final String ATTR_TIMESTAMP = "timestamp";
     92     private static final String ATTR_FLAGS = "flags";
     93     private static final String ATTR_ICON_RES_ID = "icon-res";
     94     private static final String ATTR_ICON_RES_NAME = "icon-resname";
     95     private static final String ATTR_BITMAP_PATH = "bitmap-path";
     96 
     97     private static final String NAME_CATEGORIES = "categories";
     98 
     99     private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array";
    100     private static final String ATTR_NAME_XMLUTILS = "name";
    101 
    102     private static final String KEY_DYNAMIC = "dynamic";
    103     private static final String KEY_MANIFEST = "manifest";
    104     private static final String KEY_PINNED = "pinned";
    105     private static final String KEY_BITMAPS = "bitmaps";
    106     private static final String KEY_BITMAP_BYTES = "bitmapBytes";
    107 
    108     /**
    109      * All the shortcuts from the package, keyed on IDs.
    110      */
    111     final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
    112 
    113     /**
    114      * # of times the package has called rate-limited APIs.
    115      */
    116     private int mApiCallCount;
    117 
    118     /**
    119      * When {@link #mApiCallCount} was reset last time.
    120      */
    121     private long mLastResetTime;
    122 
    123     private final int mPackageUid;
    124 
    125     private long mLastKnownForegroundElapsedTime;
    126 
    127     private ShortcutPackage(ShortcutUser shortcutUser,
    128             int packageUserId, String packageName, ShortcutPackageInfo spi) {
    129         super(shortcutUser, packageUserId, packageName,
    130                 spi != null ? spi : ShortcutPackageInfo.newEmpty());
    131 
    132         mPackageUid = shortcutUser.mService.injectGetPackageUid(packageName, packageUserId);
    133     }
    134 
    135     public ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName) {
    136         this(shortcutUser, packageUserId, packageName, null);
    137     }
    138 
    139     @Override
    140     public int getOwnerUserId() {
    141         // For packages, always owner user == package user.
    142         return getPackageUserId();
    143     }
    144 
    145     public int getPackageUid() {
    146         return mPackageUid;
    147     }
    148 
    149     @Nullable
    150     public Resources getPackageResources() {
    151         return mShortcutUser.mService.injectGetResourcesForApplicationAsUser(
    152                 getPackageName(), getPackageUserId());
    153     }
    154 
    155     public int getShortcutCount() {
    156         return mShortcuts.size();
    157     }
    158 
    159     @Override
    160     protected boolean canRestoreAnyVersion() {
    161         return false;
    162     }
    163 
    164     @Override
    165     protected void onRestored(int restoreBlockReason) {
    166         // Shortcuts have been restored.
    167         // - Unshadow all shortcuts.
    168         // - Set disabled reason.
    169         // - Disable if needed.
    170         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    171             ShortcutInfo si = mShortcuts.valueAt(i);
    172             si.clearFlags(ShortcutInfo.FLAG_SHADOW);
    173 
    174             si.setDisabledReason(restoreBlockReason);
    175             if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
    176                 si.addFlags(ShortcutInfo.FLAG_DISABLED);
    177             }
    178         }
    179         // Because some launchers may not have been restored (e.g. allowBackup=false),
    180         // we need to re-calculate the pinned shortcuts.
    181         refreshPinnedFlags();
    182     }
    183 
    184     /**
    185      * Note this does *not* provide a correct view to the calling launcher.
    186      */
    187     @Nullable
    188     public ShortcutInfo findShortcutById(String id) {
    189         return mShortcuts.get(id);
    190     }
    191 
    192     public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
    193         ShortcutInfo si = findShortcutById(id);
    194         return si != null && !si.isVisibleToPublisher();
    195     }
    196 
    197     public boolean isShortcutExistsAndVisibleToPublisher(String id) {
    198         ShortcutInfo si = findShortcutById(id);
    199         return si != null && si.isVisibleToPublisher();
    200     }
    201 
    202     private void ensureNotImmutable(@Nullable ShortcutInfo shortcut, boolean ignoreInvisible) {
    203         if (shortcut != null && shortcut.isImmutable()
    204                 && (!ignoreInvisible || shortcut.isVisibleToPublisher())) {
    205             throw new IllegalArgumentException(
    206                     "Manifest shortcut ID=" + shortcut.getId()
    207                             + " may not be manipulated via APIs");
    208         }
    209     }
    210 
    211     public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
    212         ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
    213     }
    214 
    215     public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
    216             boolean ignoreInvisible) {
    217         for (int i = shortcutIds.size() - 1; i >= 0; i--) {
    218             ensureNotImmutable(shortcutIds.get(i), ignoreInvisible);
    219         }
    220     }
    221 
    222     public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts,
    223             boolean ignoreInvisible) {
    224         for (int i = shortcuts.size() - 1; i >= 0; i--) {
    225             ensureNotImmutable(shortcuts.get(i).getId(), ignoreInvisible);
    226         }
    227     }
    228 
    229     /**
    230      * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
    231      */
    232     private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
    233         final ShortcutInfo shortcut = mShortcuts.remove(id);
    234         if (shortcut != null) {
    235             mShortcutUser.mService.removeIconLocked(shortcut);
    236             shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
    237                     | ShortcutInfo.FLAG_MANIFEST);
    238         }
    239         return shortcut;
    240     }
    241 
    242     /**
    243      * Force replace a shortcut. If there's already a shortcut with the same ID, it'll be removed,
    244      * even if it's invisible.
    245      */
    246     private void forceReplaceShortcutInner(@NonNull ShortcutInfo newShortcut) {
    247         final ShortcutService s = mShortcutUser.mService;
    248 
    249         forceDeleteShortcutInner(newShortcut.getId());
    250 
    251         // Extract Icon and update the icon res ID and the bitmap path.
    252         s.saveIconAndFixUpShortcutLocked(newShortcut);
    253         s.fixUpShortcutResourceNamesAndValues(newShortcut);
    254         mShortcuts.put(newShortcut.getId(), newShortcut);
    255     }
    256 
    257     /**
    258      * Add a shortcut. If there's already a one with the same ID, it'll be removed, even if it's
    259      * invisible.
    260      *
    261      * It checks the max number of dynamic shortcuts.
    262      */
    263     public void addOrReplaceDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
    264 
    265         Preconditions.checkArgument(newShortcut.isEnabled(),
    266                 "add/setDynamicShortcuts() cannot publish disabled shortcuts");
    267 
    268         newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
    269 
    270         final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
    271 
    272         final boolean wasPinned;
    273 
    274         if (oldShortcut == null) {
    275             wasPinned = false;
    276         } else {
    277             // It's an update case.
    278             // Make sure the target is updatable. (i.e. should be mutable.)
    279             oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
    280 
    281             wasPinned = oldShortcut.isPinned();
    282         }
    283 
    284         // If it was originally pinned, the new one should be pinned too.
    285         if (wasPinned) {
    286             newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
    287         }
    288 
    289         forceReplaceShortcutInner(newShortcut);
    290     }
    291 
    292     /**
    293      * Remove all shortcuts that aren't pinned nor dynamic.
    294      */
    295     private void removeOrphans() {
    296         ArrayList<String> removeList = null; // Lazily initialize.
    297 
    298         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    299             final ShortcutInfo si = mShortcuts.valueAt(i);
    300 
    301             if (si.isAlive()) continue;
    302 
    303             if (removeList == null) {
    304                 removeList = new ArrayList<>();
    305             }
    306             removeList.add(si.getId());
    307         }
    308         if (removeList != null) {
    309             for (int i = removeList.size() - 1; i >= 0; i--) {
    310                 forceDeleteShortcutInner(removeList.get(i));
    311             }
    312         }
    313     }
    314 
    315     /**
    316      * Remove all dynamic shortcuts.
    317      */
    318     public void deleteAllDynamicShortcuts(boolean ignoreInvisible) {
    319         final long now = mShortcutUser.mService.injectCurrentTimeMillis();
    320 
    321         boolean changed = false;
    322         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    323             final ShortcutInfo si = mShortcuts.valueAt(i);
    324             if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
    325                 changed = true;
    326 
    327                 si.setTimestamp(now);
    328                 si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
    329                 si.setRank(0); // It may still be pinned, so clear the rank.
    330             }
    331         }
    332         if (changed) {
    333             removeOrphans();
    334         }
    335     }
    336 
    337     /**
    338      * Remove a dynamic shortcut by ID.  It'll be removed from the dynamic set, but if the shortcut
    339      * is pinned, it'll remain as a pinned shortcut, and is still enabled.
    340      *
    341      * @return true if it's actually removed because it wasn't pinned, or false if it's still
    342      * pinned.
    343      */
    344     public boolean deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
    345         final ShortcutInfo removed = deleteOrDisableWithId(
    346                 shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible,
    347                 ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
    348         return removed == null;
    349     }
    350 
    351     /**
    352      * Disable a dynamic shortcut by ID.  It'll be removed from the dynamic set, but if the shortcut
    353      * is pinned, it'll remain as a pinned shortcut, but will be disabled.
    354      *
    355      * @return true if it's actually removed because it wasn't pinned, or false if it's still
    356      * pinned.
    357      */
    358     private boolean disableDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible,
    359             int disabledReason) {
    360         final ShortcutInfo disabled = deleteOrDisableWithId(
    361                 shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false, ignoreInvisible,
    362                 disabledReason);
    363         return disabled == null;
    364     }
    365 
    366     /**
    367      * Disable a dynamic shortcut by ID.  It'll be removed from the dynamic set, but if the shortcut
    368      * is pinned, it'll remain as a pinned shortcut but will be disabled.
    369      */
    370     public void disableWithId(@NonNull String shortcutId, String disabledMessage,
    371             int disabledMessageResId, boolean overrideImmutable, boolean ignoreInvisible,
    372             int disabledReason) {
    373         final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
    374                 overrideImmutable, ignoreInvisible, disabledReason);
    375 
    376         if (disabled != null) {
    377             if (disabledMessage != null) {
    378                 disabled.setDisabledMessage(disabledMessage);
    379             } else if (disabledMessageResId != 0) {
    380                 disabled.setDisabledMessageResId(disabledMessageResId);
    381 
    382                 mShortcutUser.mService.fixUpShortcutResourceNamesAndValues(disabled);
    383             }
    384         }
    385     }
    386 
    387     @Nullable
    388     private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
    389             boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) {
    390         Preconditions.checkState(
    391                 (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
    392                 "disable and disabledReason disagree: " + disable + " vs " + disabledReason);
    393         final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
    394 
    395         if (oldShortcut == null || !oldShortcut.isEnabled()
    396                 && (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
    397             return null; // Doesn't exist or already disabled.
    398         }
    399         if (!overrideImmutable) {
    400             ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true);
    401         }
    402         if (oldShortcut.isPinned()) {
    403 
    404             oldShortcut.setRank(0);
    405             oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
    406             if (disable) {
    407                 oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
    408                 // Do not overwrite the disabled reason if one is alreay set.
    409                 if (oldShortcut.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
    410                     oldShortcut.setDisabledReason(disabledReason);
    411                 }
    412             }
    413             oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
    414 
    415             // See ShortcutRequestPinProcessor.directPinShortcut().
    416             if (mShortcutUser.mService.isDummyMainActivity(oldShortcut.getActivity())) {
    417                 oldShortcut.setActivity(null);
    418             }
    419 
    420             return oldShortcut;
    421         } else {
    422             forceDeleteShortcutInner(shortcutId);
    423             return null;
    424         }
    425     }
    426 
    427     public void enableWithId(@NonNull String shortcutId) {
    428         final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
    429         if (shortcut != null) {
    430             ensureNotImmutable(shortcut, /*ignoreInvisible=*/ true);
    431             shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
    432             shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
    433         }
    434     }
    435 
    436     public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
    437         final ShortcutInfo source = mShortcuts.get(shortcut.getId());
    438         Preconditions.checkNotNull(source);
    439 
    440         mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
    441 
    442         shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
    443 
    444         forceReplaceShortcutInner(shortcut);
    445 
    446         adjustRanks();
    447     }
    448 
    449     /**
    450      * Called after a launcher updates the pinned set.  For each shortcut in this package,
    451      * set FLAG_PINNED if any launcher has pinned it.  Otherwise, clear it.
    452      *
    453      * <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
    454      */
    455     public void refreshPinnedFlags() {
    456         // First, un-pin all shortcuts
    457         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    458             mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
    459         }
    460 
    461         // Then, for the pinned set for each launcher, set the pin flag one by one.
    462         mShortcutUser.forAllLaunchers(launcherShortcuts -> {
    463             final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
    464                     getPackageName(), getPackageUserId());
    465 
    466             if (pinned == null || pinned.size() == 0) {
    467                 return;
    468             }
    469             for (int i = pinned.size() - 1; i >= 0; i--) {
    470                 final String id = pinned.valueAt(i);
    471                 final ShortcutInfo si = mShortcuts.get(id);
    472                 if (si == null) {
    473                     // This happens if a launcher pinned shortcuts from this package, then backup&
    474                     // restored, but this package doesn't allow backing up.
    475                     // In that case the launcher ends up having a dangling pinned shortcuts.
    476                     // That's fine, when the launcher is restored, we'll fix it.
    477                     continue;
    478                 }
    479                 si.addFlags(ShortcutInfo.FLAG_PINNED);
    480             }
    481         });
    482 
    483         // Lastly, remove the ones that are no longer pinned nor dynamic.
    484         removeOrphans();
    485     }
    486 
    487     /**
    488      * Number of calls that the caller has made, since the last reset.
    489      *
    490      * <p>This takes care of the resetting the counter for foreground apps as well as after
    491      * locale changes.
    492      */
    493     public int getApiCallCount(boolean unlimited) {
    494         final ShortcutService s = mShortcutUser.mService;
    495 
    496         // Reset the counter if:
    497         // - the package is in foreground now.
    498         // - the package is *not* in foreground now, but was in foreground at some point
    499         // since the previous time it had been.
    500         if (s.isUidForegroundLocked(mPackageUid)
    501                 || (mLastKnownForegroundElapsedTime
    502                     < s.getUidLastForegroundElapsedTimeLocked(mPackageUid))
    503                 || unlimited) {
    504             mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
    505             resetRateLimiting();
    506         }
    507 
    508         // Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount,
    509         // but we just can't return 0 at this point, because we may have to update
    510         // mLastResetTime.
    511 
    512         final long last = s.getLastResetTimeLocked();
    513 
    514         final long now = s.injectCurrentTimeMillis();
    515         if (ShortcutService.isClockValid(now) && mLastResetTime > now) {
    516             Slog.w(TAG, "Clock rewound");
    517             // Clock rewound.
    518             mLastResetTime = now;
    519             mApiCallCount = 0;
    520             return mApiCallCount;
    521         }
    522 
    523         // If not reset yet, then reset.
    524         if (mLastResetTime < last) {
    525             if (ShortcutService.DEBUG) {
    526                 Slog.d(TAG, String.format("%s: last reset=%d, now=%d, last=%d: resetting",
    527                         getPackageName(), mLastResetTime, now, last));
    528             }
    529             mApiCallCount = 0;
    530             mLastResetTime = last;
    531         }
    532         return mApiCallCount;
    533     }
    534 
    535     /**
    536      * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
    537      * and return true.  Otherwise just return false.
    538      *
    539      * <p>This takes care of the resetting the counter for foreground apps as well as after
    540      * locale changes, which is done internally by {@link #getApiCallCount}.
    541      */
    542     public boolean tryApiCall(boolean unlimited) {
    543         final ShortcutService s = mShortcutUser.mService;
    544 
    545         if (getApiCallCount(unlimited) >= s.mMaxUpdatesPerInterval) {
    546             return false;
    547         }
    548         mApiCallCount++;
    549         s.scheduleSaveUser(getOwnerUserId());
    550         return true;
    551     }
    552 
    553     public void resetRateLimiting() {
    554         if (ShortcutService.DEBUG) {
    555             Slog.d(TAG, "resetRateLimiting: " + getPackageName());
    556         }
    557         if (mApiCallCount > 0) {
    558             mApiCallCount = 0;
    559             mShortcutUser.mService.scheduleSaveUser(getOwnerUserId());
    560         }
    561     }
    562 
    563     public void resetRateLimitingForCommandLineNoSaving() {
    564         mApiCallCount = 0;
    565         mLastResetTime = 0;
    566     }
    567 
    568     /**
    569      * Find all shortcuts that match {@code query}.
    570      */
    571     public void findAll(@NonNull List<ShortcutInfo> result,
    572             @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
    573         findAll(result, query, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
    574     }
    575 
    576     /**
    577      * Find all shortcuts that match {@code query}.
    578      *
    579      * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned
    580      * by the calling launcher will not be included in the result, and also "isPinned" will be
    581      * adjusted for the caller too.
    582      */
    583     public void findAll(@NonNull List<ShortcutInfo> result,
    584             @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
    585             @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) {
    586         if (getPackageInfo().isShadow()) {
    587             // Restored and the app not installed yet, so don't return any.
    588             return;
    589         }
    590 
    591         final ShortcutService s = mShortcutUser.mService;
    592 
    593         // Set of pinned shortcuts by the calling launcher.
    594         final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
    595                 : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
    596                     .getPinnedShortcutIds(getPackageName(), getPackageUserId());
    597 
    598         for (int i = 0; i < mShortcuts.size(); i++) {
    599             final ShortcutInfo si = mShortcuts.valueAt(i);
    600 
    601             // Need to adjust PINNED flag depending on the caller.
    602             // Basically if the caller is a launcher (callingLauncher != null) and the launcher
    603             // isn't pinning it, then we need to clear PINNED for this caller.
    604             final boolean isPinnedByCaller = (callingLauncher == null)
    605                     || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
    606 
    607             if (!getPinnedByAnyLauncher) {
    608                 if (si.isFloating()) {
    609                     if (!isPinnedByCaller) {
    610                         continue;
    611                     }
    612                 }
    613             }
    614             final ShortcutInfo clone = si.clone(cloneFlag);
    615 
    616             // Fix up isPinned for the caller.  Note we need to do it before the "test" callback,
    617             // since it may check isPinned.
    618             // However, if getPinnedByAnyLauncher is set, we do it after the test.
    619             if (!getPinnedByAnyLauncher) {
    620                 if (!isPinnedByCaller) {
    621                     clone.clearFlags(ShortcutInfo.FLAG_PINNED);
    622                 }
    623             }
    624             if (query == null || query.test(clone)) {
    625                 if (!isPinnedByCaller) {
    626                     clone.clearFlags(ShortcutInfo.FLAG_PINNED);
    627                 }
    628                 result.add(clone);
    629             }
    630         }
    631     }
    632 
    633     public void resetThrottling() {
    634         mApiCallCount = 0;
    635     }
    636 
    637     /**
    638      * Return the filenames (excluding path names) of icon bitmap files from this package.
    639      */
    640     public ArraySet<String> getUsedBitmapFiles() {
    641         final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
    642 
    643         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    644             final ShortcutInfo si = mShortcuts.valueAt(i);
    645             if (si.getBitmapPath() != null) {
    646                 usedFiles.add(getFileName(si.getBitmapPath()));
    647             }
    648         }
    649         return usedFiles;
    650     }
    651 
    652     private static String getFileName(@NonNull String path) {
    653         final int sep = path.lastIndexOf(File.separatorChar);
    654         if (sep == -1) {
    655             return path;
    656         } else {
    657             return path.substring(sep + 1);
    658         }
    659     }
    660 
    661     /**
    662      * @return false if any of the target activities are no longer enabled.
    663      */
    664     private boolean areAllActivitiesStillEnabled() {
    665         if (mShortcuts.size() == 0) {
    666             return true;
    667         }
    668         final ShortcutService s = mShortcutUser.mService;
    669 
    670         // Normally the number of target activities is 1 or so, so no need to use a complex
    671         // structure like a set.
    672         final ArrayList<ComponentName> checked = new ArrayList<>(4);
    673 
    674         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    675             final ShortcutInfo si = mShortcuts.valueAt(i);
    676             final ComponentName activity = si.getActivity();
    677 
    678             if (checked.contains(activity)) {
    679                 continue; // Already checked.
    680             }
    681             checked.add(activity);
    682 
    683             if ((activity != null)
    684                     && !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
    685                 return false;
    686             }
    687         }
    688         return true;
    689     }
    690 
    691     /**
    692      * Called when the package may be added or updated, or its activities may be disabled, and
    693      * if so, rescan the package and do the necessary stuff.
    694      *
    695      * Add case:
    696      * - Publish manifest shortcuts.
    697      *
    698      * Update case:
    699      * - Re-publish manifest shortcuts.
    700      * - If there are shortcuts with resources (icons or strings), update their timestamps.
    701      * - Disable shortcuts whose target activities are disabled.
    702      *
    703      * @return TRUE if any shortcuts have been changed.
    704      */
    705     public boolean rescanPackageIfNeeded(boolean isNewApp, boolean forceRescan) {
    706         final ShortcutService s = mShortcutUser.mService;
    707         final long start = s.getStatStartTime();
    708 
    709         final PackageInfo pi;
    710         try {
    711             pi = mShortcutUser.mService.getPackageInfo(
    712                     getPackageName(), getPackageUserId());
    713             if (pi == null) {
    714                 return false; // Shouldn't happen.
    715             }
    716 
    717             if (!isNewApp && !forceRescan) {
    718                 // Return if the package hasn't changed, ie:
    719                 // - version code hasn't change
    720                 // - lastUpdateTime hasn't change
    721                 // - all target activities are still enabled.
    722 
    723                 // Note, system apps timestamps do *not* change after OTAs.  (But they do
    724                 // after an adb sync or a local flash.)
    725                 // This means if a system app's version code doesn't change on an OTA,
    726                 // we don't notice it's updated.  But that's fine since their version code *should*
    727                 // really change on OTAs.
    728                 if ((getPackageInfo().getVersionCode() == pi.getLongVersionCode())
    729                         && (getPackageInfo().getLastUpdateTime() == pi.lastUpdateTime)
    730                         && areAllActivitiesStillEnabled()) {
    731                     return false;
    732                 }
    733             }
    734         } finally {
    735             s.logDurationStat(Stats.PACKAGE_UPDATE_CHECK, start);
    736         }
    737 
    738         // Now prepare to publish manifest shortcuts.
    739         List<ShortcutInfo> newManifestShortcutList = null;
    740         try {
    741             newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
    742                     getPackageName(), getPackageUserId());
    743         } catch (IOException|XmlPullParserException e) {
    744             Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
    745         }
    746         final int manifestShortcutSize = newManifestShortcutList == null ? 0
    747                 : newManifestShortcutList.size();
    748         if (ShortcutService.DEBUG) {
    749             Slog.d(TAG, String.format("Package %s has %d manifest shortcut(s)",
    750                     getPackageName(), manifestShortcutSize));
    751         }
    752         if (isNewApp && (manifestShortcutSize == 0)) {
    753             // If it's a new app, and it doesn't have manifest shortcuts, then nothing to do.
    754 
    755             // If it's an update, then it may already have manifest shortcuts, which need to be
    756             // disabled.
    757             return false;
    758         }
    759         if (ShortcutService.DEBUG) {
    760             Slog.d(TAG, String.format("Package %s %s, version %d -> %d", getPackageName(),
    761                     (isNewApp ? "added" : "updated"),
    762                     getPackageInfo().getVersionCode(), pi.getLongVersionCode()));
    763         }
    764 
    765         getPackageInfo().updateFromPackageInfo(pi);
    766         final long newVersionCode = getPackageInfo().getVersionCode();
    767 
    768         // See if there are any shortcuts that were prevented restoring because the app was of a
    769         // lower version, and re-enable them.
    770         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    771             final ShortcutInfo si = mShortcuts.valueAt(i);
    772             if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
    773                 continue;
    774             }
    775             if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
    776                 if (ShortcutService.DEBUG) {
    777                     Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
    778                             si.getId(), getPackageInfo().getBackupSourceVersionCode()));
    779                 }
    780                 continue;
    781             }
    782             Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
    783             si.clearFlags(ShortcutInfo.FLAG_DISABLED);
    784             si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
    785         }
    786 
    787         // For existing shortcuts, update timestamps if they have any resources.
    788         // Also check if shortcuts' activities are still main activities.  Otherwise, disable them.
    789         if (!isNewApp) {
    790             Resources publisherRes = null;
    791 
    792             for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    793                 final ShortcutInfo si = mShortcuts.valueAt(i);
    794 
    795                 // Disable dynamic shortcuts whose target activity is gone.
    796                 if (si.isDynamic()) {
    797                     if (si.getActivity() == null) {
    798                         // Note if it's dynamic, it must have a target activity, but b/36228253.
    799                         s.wtf("null activity detected.");
    800                         // TODO Maybe remove it?
    801                     } else if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) {
    802                         Slog.w(TAG, String.format(
    803                                 "%s is no longer main activity. Disabling shorcut %s.",
    804                                 getPackageName(), si.getId()));
    805                         if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
    806                                 ShortcutInfo.DISABLED_REASON_APP_CHANGED)) {
    807                             continue; // Actually removed.
    808                         }
    809                         // Still pinned, so fall-through and possibly update the resources.
    810                     }
    811                 }
    812 
    813                 if (si.hasAnyResources()) {
    814                     if (!si.isOriginallyFromManifest()) {
    815                         if (publisherRes == null) {
    816                             publisherRes = getPackageResources();
    817                             if (publisherRes == null) {
    818                                 break; // Resources couldn't be loaded.
    819                             }
    820                         }
    821 
    822                         // If this shortcut is not from a manifest, then update all resource IDs
    823                         // from resource names.  (We don't allow resource strings for
    824                         // non-manifest at the moment, but icons can still be resources.)
    825                         si.lookupAndFillInResourceIds(publisherRes);
    826                     }
    827                     si.setTimestamp(s.injectCurrentTimeMillis());
    828                 }
    829             }
    830         }
    831 
    832         // (Re-)publish manifest shortcut.
    833         publishManifestShortcuts(newManifestShortcutList);
    834 
    835         if (newManifestShortcutList != null) {
    836             pushOutExcessShortcuts();
    837         }
    838 
    839         s.verifyStates();
    840 
    841         // This will send a notification to the launcher, and also save .
    842         s.packageShortcutsChanged(getPackageName(), getPackageUserId());
    843         return true; // true means changed.
    844     }
    845 
    846     private boolean publishManifestShortcuts(List<ShortcutInfo> newManifestShortcutList) {
    847         if (ShortcutService.DEBUG) {
    848             Slog.d(TAG, String.format(
    849                     "Package %s: publishing manifest shortcuts", getPackageName()));
    850         }
    851         boolean changed = false;
    852 
    853         // Keep the previous IDs.
    854         ArraySet<String> toDisableList = null;
    855         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
    856             final ShortcutInfo si = mShortcuts.valueAt(i);
    857 
    858             if (si.isManifestShortcut()) {
    859                 if (toDisableList == null) {
    860                     toDisableList = new ArraySet<>();
    861                 }
    862                 toDisableList.add(si.getId());
    863             }
    864         }
    865 
    866         // Publish new ones.
    867         if (newManifestShortcutList != null) {
    868             final int newListSize = newManifestShortcutList.size();
    869 
    870             for (int i = 0; i < newListSize; i++) {
    871                 changed = true;
    872 
    873                 final ShortcutInfo newShortcut = newManifestShortcutList.get(i);
    874                 final boolean newDisabled = !newShortcut.isEnabled();
    875 
    876                 final String id = newShortcut.getId();
    877                 final ShortcutInfo oldShortcut = mShortcuts.get(id);
    878 
    879                 boolean wasPinned = false;
    880 
    881                 if (oldShortcut != null) {
    882                     if (!oldShortcut.isOriginallyFromManifest()) {
    883                         Slog.e(TAG, "Shortcut with ID=" + newShortcut.getId()
    884                                 + " exists but is not from AndroidManifest.xml, not updating.");
    885                         continue;
    886                     }
    887                     // Take over the pinned flag.
    888                     if (oldShortcut.isPinned()) {
    889                         wasPinned = true;
    890                         newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
    891                     }
    892                 }
    893                 if (newDisabled && !wasPinned) {
    894                     // If the shortcut is disabled, and it was *not* pinned, then this
    895                     // just doesn't have to be published.
    896                     // Just keep it in toDisableList, so the previous one would be removed.
    897                     continue;
    898                 }
    899 
    900                 // Note even if enabled=false, we still need to update all fields, so do it
    901                 // regardless.
    902                 forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
    903 
    904                 if (!newDisabled && toDisableList != null) {
    905                     // Still alive, don't remove.
    906                     toDisableList.remove(id);
    907                 }
    908             }
    909         }
    910 
    911         // Disable the previous manifest shortcuts that are no longer in the manifest.
    912         if (toDisableList != null) {
    913             if (ShortcutService.DEBUG) {
    914                 Slog.d(TAG, String.format(
    915                         "Package %s: disabling %d stale shortcuts", getPackageName(),
    916                         toDisableList.size()));
    917             }
    918             for (int i = toDisableList.size() - 1; i >= 0; i--) {
    919                 changed = true;
    920 
    921                 final String id = toDisableList.valueAt(i);
    922 
    923                 disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
    924                         /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
    925                         ShortcutInfo.DISABLED_REASON_APP_CHANGED);
    926             }
    927             removeOrphans();
    928         }
    929         adjustRanks();
    930         return changed;
    931     }
    932 
    933     /**
    934      * For each target activity, make sure # of dynamic + manifest shortcuts <= max.
    935      * If too many, we'll remove the dynamic with the lowest ranks.
    936      */
    937     private boolean pushOutExcessShortcuts() {
    938         final ShortcutService service = mShortcutUser.mService;
    939         final int maxShortcuts = service.getMaxActivityShortcuts();
    940 
    941         boolean changed = false;
    942 
    943         final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
    944                 sortShortcutsToActivities();
    945         for (int outer = all.size() - 1; outer >= 0; outer--) {
    946             final ArrayList<ShortcutInfo> list = all.valueAt(outer);
    947             if (list.size() <= maxShortcuts) {
    948                 continue;
    949             }
    950             // Sort by isManifestShortcut() and getRank().
    951             Collections.sort(list, mShortcutTypeAndRankComparator);
    952 
    953             // Keep [0 .. max), and remove (as dynamic) [max .. size)
    954             for (int inner = list.size() - 1; inner >= maxShortcuts; inner--) {
    955                 final ShortcutInfo shortcut = list.get(inner);
    956 
    957                 if (shortcut.isManifestShortcut()) {
    958                     // This shouldn't happen -- excess shortcuts should all be non-manifest.
    959                     // But just in case.
    960                     service.wtf("Found manifest shortcuts in excess list.");
    961                     continue;
    962                 }
    963                 deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true);
    964             }
    965         }
    966 
    967         return changed;
    968     }
    969 
    970     /**
    971      * To sort by isManifestShortcut() and getRank(). i.e.  manifest shortcuts come before
    972      * non-manifest shortcuts, then sort by rank.
    973      *
    974      * This is used to decide which dynamic shortcuts to remove when an upgraded version has more
    975      * manifest shortcuts than before and as a result we need to remove some of the dynamic
    976      * shortcuts.  We sort manifest + dynamic shortcuts by this order, and remove the ones with
    977      * the last ones.
    978      *
    979      * (Note the number of manifest shortcuts is always <= the max number, because if there are
    980      * more, ShortcutParser would ignore the rest.)
    981      */
    982     final Comparator<ShortcutInfo> mShortcutTypeAndRankComparator = (ShortcutInfo a,
    983             ShortcutInfo b) -> {
    984         if (a.isManifestShortcut() && !b.isManifestShortcut()) {
    985             return -1;
    986         }
    987         if (!a.isManifestShortcut() && b.isManifestShortcut()) {
    988             return 1;
    989         }
    990         return Integer.compare(a.getRank(), b.getRank());
    991     };
    992 
    993     /**
    994      * Build a list of shortcuts for each target activity and return as a map. The result won't
    995      * contain "floating" shortcuts because they don't belong on any activities.
    996      */
    997     private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
    998         final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
    999                 = new ArrayMap<>();
   1000         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
   1001             final ShortcutInfo si = mShortcuts.valueAt(i);
   1002             if (si.isFloating()) {
   1003                 continue; // Ignore floating shortcuts, which are not tied to any activities.
   1004             }
   1005 
   1006             final ComponentName activity = si.getActivity();
   1007             if (activity == null) {
   1008                 mShortcutUser.mService.wtf("null activity detected.");
   1009                 continue;
   1010             }
   1011 
   1012             ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
   1013             if (list == null) {
   1014                 list = new ArrayList<>();
   1015                 activitiesToShortcuts.put(activity, list);
   1016             }
   1017             list.add(si);
   1018         }
   1019         return activitiesToShortcuts;
   1020     }
   1021 
   1022     /** Used by {@link #enforceShortcutCountsBeforeOperation} */
   1023     private void incrementCountForActivity(ArrayMap<ComponentName, Integer> counts,
   1024             ComponentName cn, int increment) {
   1025         Integer oldValue = counts.get(cn);
   1026         if (oldValue == null) {
   1027             oldValue = 0;
   1028         }
   1029 
   1030         counts.put(cn, oldValue + increment);
   1031     }
   1032 
   1033     /**
   1034      * Called by
   1035      * {@link android.content.pm.ShortcutManager#setDynamicShortcuts},
   1036      * {@link android.content.pm.ShortcutManager#addDynamicShortcuts}, and
   1037      * {@link android.content.pm.ShortcutManager#updateShortcuts} before actually performing
   1038      * the operation to make sure the operation wouldn't result in the target activities having
   1039      * more than the allowed number of dynamic/manifest shortcuts.
   1040      *
   1041      * @param newList shortcut list passed to set, add or updateShortcuts().
   1042      * @param operation add, set or update.
   1043      * @throws IllegalArgumentException if the operation would result in going over the max
   1044      *                                  shortcut count for any activity.
   1045      */
   1046     public void enforceShortcutCountsBeforeOperation(List<ShortcutInfo> newList,
   1047             @ShortcutOperation int operation) {
   1048         final ShortcutService service = mShortcutUser.mService;
   1049 
   1050         // Current # of dynamic / manifest shortcuts for each activity.
   1051         // (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
   1052         // anyway.)
   1053         final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
   1054         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
   1055             final ShortcutInfo shortcut = mShortcuts.valueAt(i);
   1056 
   1057             if (shortcut.isManifestShortcut()) {
   1058                 incrementCountForActivity(counts, shortcut.getActivity(), 1);
   1059             } else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
   1060                 incrementCountForActivity(counts, shortcut.getActivity(), 1);
   1061             }
   1062         }
   1063 
   1064         for (int i = newList.size() - 1; i >= 0; i--) {
   1065             final ShortcutInfo newShortcut = newList.get(i);
   1066             final ComponentName newActivity = newShortcut.getActivity();
   1067             if (newActivity == null) {
   1068                 if (operation != ShortcutService.OPERATION_UPDATE) {
   1069                     service.wtf("Activity must not be null at this point");
   1070                     continue; // Just ignore this invalid case.
   1071                 }
   1072                 continue; // Activity can be null for update.
   1073             }
   1074 
   1075             final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
   1076             if (original == null) {
   1077                 if (operation == ShortcutService.OPERATION_UPDATE) {
   1078                     continue; // When updating, ignore if there's no target.
   1079                 }
   1080                 // Add() or set(), and there's no existing shortcut with the same ID.  We're
   1081                 // simply publishing (as opposed to updating) this shortcut, so just +1.
   1082                 incrementCountForActivity(counts, newActivity, 1);
   1083                 continue;
   1084             }
   1085             if (original.isFloating() && (operation == ShortcutService.OPERATION_UPDATE)) {
   1086                 // Updating floating shortcuts doesn't affect the count, so ignore.
   1087                 continue;
   1088             }
   1089 
   1090             // If it's add() or update(), then need to decrement for the previous activity.
   1091             // Skip it for set() since it's already been taken care of by not counting the original
   1092             // dynamic shortcuts in the first loop.
   1093             if (operation != ShortcutService.OPERATION_SET) {
   1094                 final ComponentName oldActivity = original.getActivity();
   1095                 if (!original.isFloating()) {
   1096                     incrementCountForActivity(counts, oldActivity, -1);
   1097                 }
   1098             }
   1099             incrementCountForActivity(counts, newActivity, 1);
   1100         }
   1101 
   1102         // Then make sure none of the activities have more than the max number of shortcuts.
   1103         for (int i = counts.size() - 1; i >= 0; i--) {
   1104             service.enforceMaxActivityShortcuts(counts.valueAt(i));
   1105         }
   1106     }
   1107 
   1108     /**
   1109      * For all the text fields, refresh the string values if they're from resources.
   1110      */
   1111     public void resolveResourceStrings() {
   1112         final ShortcutService s = mShortcutUser.mService;
   1113         boolean changed = false;
   1114 
   1115         Resources publisherRes = null;
   1116         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
   1117             final ShortcutInfo si = mShortcuts.valueAt(i);
   1118 
   1119             if (si.hasStringResources()) {
   1120                 changed = true;
   1121 
   1122                 if (publisherRes == null) {
   1123                     publisherRes = getPackageResources();
   1124                     if (publisherRes == null) {
   1125                         break; // Resources couldn't be loaded.
   1126                     }
   1127                 }
   1128 
   1129                 si.resolveResourceStrings(publisherRes);
   1130                 si.setTimestamp(s.injectCurrentTimeMillis());
   1131             }
   1132         }
   1133         if (changed) {
   1134             s.packageShortcutsChanged(getPackageName(), getPackageUserId());
   1135         }
   1136     }
   1137 
   1138     /** Clears the implicit ranks for all shortcuts. */
   1139     public void clearAllImplicitRanks() {
   1140         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
   1141             final ShortcutInfo si = mShortcuts.valueAt(i);
   1142             si.clearImplicitRankAndRankChangedFlag();
   1143         }
   1144     }
   1145 
   1146     /**
   1147      * Used to sort shortcuts for rank auto-adjusting.
   1148      */
   1149     final Comparator<ShortcutInfo> mShortcutRankComparator = (ShortcutInfo a, ShortcutInfo b) -> {
   1150         // First, sort by rank.
   1151         int ret = Integer.compare(a.getRank(), b.getRank());
   1152         if (ret != 0) {
   1153             return ret;
   1154         }
   1155         // When ranks are tie, then prioritize the ones that have just been assigned new ranks.
   1156         // e.g. when there are 3 shortcuts, "s1" "s2" and "s3" with rank 0, 1, 2 respectively,
   1157         // adding a shortcut "s4" with rank 1 will "insert" it between "s1" and "s2", because
   1158         // "s2" and "s4" have the same rank 1 but s4 has isRankChanged() set.
   1159         // Similarly, updating s3's rank to 1 will insert it between s1 and s2.
   1160         if (a.isRankChanged() != b.isRankChanged()) {
   1161             return a.isRankChanged() ? -1 : 1;
   1162         }
   1163         // If they're still tie, sort by implicit rank -- i.e. preserve the order in which
   1164         // they're passed to the API.
   1165         ret = Integer.compare(a.getImplicitRank(), b.getImplicitRank());
   1166         if (ret != 0) {
   1167             return ret;
   1168         }
   1169         // If they're still tie, just sort by their IDs.
   1170         // This may happen with updateShortcuts() -- see
   1171         // the testUpdateShortcuts_noManifestShortcuts() test.
   1172         return a.getId().compareTo(b.getId());
   1173     };
   1174 
   1175     /**
   1176      * Re-calculate the ranks for all shortcuts.
   1177      */
   1178     public void adjustRanks() {
   1179         final ShortcutService s = mShortcutUser.mService;
   1180         final long now = s.injectCurrentTimeMillis();
   1181 
   1182         // First, clear ranks for floating shortcuts.
   1183         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
   1184             final ShortcutInfo si = mShortcuts.valueAt(i);
   1185             if (si.isFloating()) {
   1186                 if (si.getRank() != 0) {
   1187                     si.setTimestamp(now);
   1188                     si.setRank(0);
   1189                 }
   1190             }
   1191         }
   1192 
   1193         // Then adjust ranks.  Ranks are unique for each activity, so we first need to sort
   1194         // shortcuts to each activity.
   1195         // Then sort the shortcuts within each activity with mShortcutRankComparator, and
   1196         // assign ranks from 0.
   1197         final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
   1198                 sortShortcutsToActivities();
   1199         for (int outer = all.size() - 1; outer >= 0; outer--) { // For each activity.
   1200             final ArrayList<ShortcutInfo> list = all.valueAt(outer);
   1201 
   1202             // Sort by ranks and other signals.
   1203             Collections.sort(list, mShortcutRankComparator);
   1204 
   1205             int rank = 0;
   1206 
   1207             final int size = list.size();
   1208             for (int i = 0; i < size; i++) {
   1209                 final ShortcutInfo si = list.get(i);
   1210                 if (si.isManifestShortcut()) {
   1211                     // Don't adjust ranks for manifest shortcuts.
   1212                     continue;
   1213                 }
   1214                 // At this point, it must be dynamic.
   1215                 if (!si.isDynamic()) {
   1216                     s.wtf("Non-dynamic shortcut found.");
   1217                     continue;
   1218                 }
   1219                 final int thisRank = rank++;
   1220                 if (si.getRank() != thisRank) {
   1221                     si.setTimestamp(now);
   1222                     si.setRank(thisRank);
   1223                 }
   1224             }
   1225         }
   1226     }
   1227 
   1228     /** @return true if there's any shortcuts that are not manifest shortcuts. */
   1229     public boolean hasNonManifestShortcuts() {
   1230         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
   1231             final ShortcutInfo si = mShortcuts.valueAt(i);
   1232             if (!si.isDeclaredInManifest()) {
   1233                 return true;
   1234             }
   1235         }
   1236         return false;
   1237     }
   1238 
   1239     public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
   1240         pw.println();
   1241 
   1242         pw.print(prefix);
   1243         pw.print("Package: ");
   1244         pw.print(getPackageName());
   1245         pw.print("  UID: ");
   1246         pw.print(mPackageUid);
   1247         pw.println();
   1248 
   1249         pw.print(prefix);
   1250         pw.print("  ");
   1251         pw.print("Calls: ");
   1252         pw.print(getApiCallCount(/*unlimited=*/ false));
   1253         pw.println();
   1254 
   1255         // getApiCallCount() may have updated mLastKnownForegroundElapsedTime.
   1256         pw.print(prefix);
   1257         pw.print("  ");
   1258         pw.print("Last known FG: ");
   1259         pw.print(mLastKnownForegroundElapsedTime);
   1260         pw.println();
   1261 
   1262         // This should be after getApiCallCount(), which may update it.
   1263         pw.print(prefix);
   1264         pw.print("  ");
   1265         pw.print("Last reset: [");
   1266         pw.print(mLastResetTime);
   1267         pw.print("] ");
   1268         pw.print(ShortcutService.formatTime(mLastResetTime));
   1269         pw.println();
   1270 
   1271         getPackageInfo().dump(pw, prefix + "  ");
   1272         pw.println();
   1273 
   1274         pw.print(prefix);
   1275         pw.println("  Shortcuts:");
   1276         long totalBitmapSize = 0;
   1277         final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
   1278         final int size = shortcuts.size();
   1279         for (int i = 0; i < size; i++) {
   1280             final ShortcutInfo si = shortcuts.valueAt(i);
   1281             pw.println(si.toDumpString(prefix + "    "));
   1282             if (si.getBitmapPath() != null) {
   1283                 final long len = new File(si.getBitmapPath()).length();
   1284                 pw.print(prefix);
   1285                 pw.print("      ");
   1286                 pw.print("bitmap size=");
   1287                 pw.println(len);
   1288 
   1289                 totalBitmapSize += len;
   1290             }
   1291         }
   1292         pw.print(prefix);
   1293         pw.print("  ");
   1294         pw.print("Total bitmap size: ");
   1295         pw.print(totalBitmapSize);
   1296         pw.print(" (");
   1297         pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
   1298         pw.println(")");
   1299     }
   1300 
   1301     @Override
   1302     public JSONObject dumpCheckin(boolean clear) throws JSONException {
   1303         final JSONObject result = super.dumpCheckin(clear);
   1304 
   1305         int numDynamic = 0;
   1306         int numPinned = 0;
   1307         int numManifest = 0;
   1308         int numBitmaps = 0;
   1309         long totalBitmapSize = 0;
   1310 
   1311         final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
   1312         final int size = shortcuts.size();
   1313         for (int i = 0; i < size; i++) {
   1314             final ShortcutInfo si = shortcuts.valueAt(i);
   1315 
   1316             if (si.isDynamic()) numDynamic++;
   1317             if (si.isDeclaredInManifest()) numManifest++;
   1318             if (si.isPinned()) numPinned++;
   1319 
   1320             if (si.getBitmapPath() != null) {
   1321                 numBitmaps++;
   1322                 totalBitmapSize += new File(si.getBitmapPath()).length();
   1323             }
   1324         }
   1325 
   1326         result.put(KEY_DYNAMIC, numDynamic);
   1327         result.put(KEY_MANIFEST, numManifest);
   1328         result.put(KEY_PINNED, numPinned);
   1329         result.put(KEY_BITMAPS, numBitmaps);
   1330         result.put(KEY_BITMAP_BYTES, totalBitmapSize);
   1331 
   1332         // TODO Log update frequency too.
   1333 
   1334         return result;
   1335     }
   1336 
   1337     @Override
   1338     public void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
   1339             throws IOException, XmlPullParserException {
   1340         final int size = mShortcuts.size();
   1341 
   1342         if (size == 0 && mApiCallCount == 0) {
   1343             return; // nothing to write.
   1344         }
   1345 
   1346         out.startTag(null, TAG_ROOT);
   1347 
   1348         ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
   1349         ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
   1350         ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
   1351         getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
   1352 
   1353         for (int j = 0; j < size; j++) {
   1354             saveShortcut(out, mShortcuts.valueAt(j), forBackup,
   1355                     getPackageInfo().isBackupAllowed());
   1356         }
   1357 
   1358         out.endTag(null, TAG_ROOT);
   1359     }
   1360 
   1361     private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup,
   1362             boolean appSupportsBackup)
   1363             throws IOException, XmlPullParserException {
   1364 
   1365         final ShortcutService s = mShortcutUser.mService;
   1366 
   1367         if (forBackup) {
   1368             if (!(si.isPinned() && si.isEnabled())) {
   1369                 // We only backup pinned shortcuts that are enabled.
   1370                 // Note, this means, shortcuts that are restored but are blocked restore, e.g. due
   1371                 // to a lower version code, will not be ported to a new device.
   1372                 return;
   1373             }
   1374         }
   1375         final boolean shouldBackupDetails =
   1376                 !forBackup // It's not backup
   1377                 || appSupportsBackup; // Or, it's a backup and app supports backup.
   1378 
   1379         // Note: at this point no shortcuts should have bitmaps pending save, but if they do,
   1380         // just remove the bitmap.
   1381         if (si.isIconPendingSave()) {
   1382             s.removeIconLocked(si);
   1383         }
   1384         out.startTag(null, TAG_SHORTCUT);
   1385         ShortcutService.writeAttr(out, ATTR_ID, si.getId());
   1386         // writeAttr(out, "package", si.getPackageName()); // not needed
   1387         ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivity());
   1388         // writeAttr(out, "icon", si.getIcon());  // We don't save it.
   1389         ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
   1390         ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
   1391         ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName());
   1392         ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
   1393         ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
   1394         ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
   1395         if (shouldBackupDetails) {
   1396             ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
   1397             ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
   1398                     si.getDisabledMessageResourceId());
   1399             ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
   1400                     si.getDisabledMessageResName());
   1401         }
   1402         ShortcutService.writeAttr(out, ATTR_DISABLED_REASON, si.getDisabledReason());
   1403         ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
   1404                 si.getLastChangedTimestamp());
   1405         if (forBackup) {
   1406             // Don't write icon information.  Also drop the dynamic flag.
   1407 
   1408             int flags = si.getFlags() &
   1409                     ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
   1410                             | ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE
   1411                             | ShortcutInfo.FLAG_DYNAMIC);
   1412             ShortcutService.writeAttr(out, ATTR_FLAGS, flags);
   1413 
   1414             // Set the publisher version code at every backup.
   1415             final long packageVersionCode = getPackageInfo().getVersionCode();
   1416             if (packageVersionCode == 0) {
   1417                 s.wtf("Package version code should be available at this point.");
   1418                 // However, 0 is a valid version code, so we just go ahead with it...
   1419             }
   1420         } else {
   1421             // When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
   1422             // as dynamic.
   1423             ShortcutService.writeAttr(out, ATTR_RANK, si.getRank());
   1424 
   1425             ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
   1426             ShortcutService.writeAttr(out, ATTR_ICON_RES_ID, si.getIconResourceId());
   1427             ShortcutService.writeAttr(out, ATTR_ICON_RES_NAME, si.getIconResName());
   1428             ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
   1429         }
   1430 
   1431         if (shouldBackupDetails) {
   1432             {
   1433                 final Set<String> cat = si.getCategories();
   1434                 if (cat != null && cat.size() > 0) {
   1435                     out.startTag(null, TAG_CATEGORIES);
   1436                     XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
   1437                             NAME_CATEGORIES, out);
   1438                     out.endTag(null, TAG_CATEGORIES);
   1439                 }
   1440             }
   1441             final Intent[] intentsNoExtras = si.getIntentsNoExtras();
   1442             final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
   1443             final int numIntents = intentsNoExtras.length;
   1444             for (int i = 0; i < numIntents; i++) {
   1445                 out.startTag(null, TAG_INTENT);
   1446                 ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
   1447                 ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
   1448                 out.endTag(null, TAG_INTENT);
   1449             }
   1450 
   1451             ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
   1452         }
   1453 
   1454         out.endTag(null, TAG_SHORTCUT);
   1455     }
   1456 
   1457     public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser,
   1458             XmlPullParser parser, boolean fromBackup)
   1459             throws IOException, XmlPullParserException {
   1460 
   1461         final String packageName = ShortcutService.parseStringAttribute(parser,
   1462                 ATTR_NAME);
   1463 
   1464         final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
   1465                 shortcutUser.getUserId(), packageName);
   1466 
   1467         ret.mApiCallCount =
   1468                 ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
   1469         ret.mLastResetTime =
   1470                 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
   1471 
   1472 
   1473         final int outerDepth = parser.getDepth();
   1474         int type;
   1475         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
   1476                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
   1477             if (type != XmlPullParser.START_TAG) {
   1478                 continue;
   1479             }
   1480             final int depth = parser.getDepth();
   1481             final String tag = parser.getName();
   1482             if (depth == outerDepth + 1) {
   1483                 switch (tag) {
   1484                     case ShortcutPackageInfo.TAG_ROOT:
   1485                         ret.getPackageInfo().loadFromXml(parser, fromBackup);
   1486 
   1487                         continue;
   1488                     case TAG_SHORTCUT:
   1489                         final ShortcutInfo si = parseShortcut(parser, packageName,
   1490                                 shortcutUser.getUserId(), fromBackup);
   1491 
   1492                         // Don't use addShortcut(), we don't need to save the icon.
   1493                         ret.mShortcuts.put(si.getId(), si);
   1494                         continue;
   1495                 }
   1496             }
   1497             ShortcutService.warnForInvalidTag(depth, tag);
   1498         }
   1499         return ret;
   1500     }
   1501 
   1502     private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName,
   1503             @UserIdInt int userId, boolean fromBackup)
   1504             throws IOException, XmlPullParserException {
   1505         String id;
   1506         ComponentName activityComponent;
   1507         // Icon icon;
   1508         String title;
   1509         int titleResId;
   1510         String titleResName;
   1511         String text;
   1512         int textResId;
   1513         String textResName;
   1514         String disabledMessage;
   1515         int disabledMessageResId;
   1516         String disabledMessageResName;
   1517         int disabledReason;
   1518         Intent intentLegacy;
   1519         PersistableBundle intentPersistableExtrasLegacy = null;
   1520         ArrayList<Intent> intents = new ArrayList<>();
   1521         int rank;
   1522         PersistableBundle extras = null;
   1523         long lastChangedTimestamp;
   1524         int flags;
   1525         int iconResId;
   1526         String iconResName;
   1527         String bitmapPath;
   1528         int backupVersionCode;
   1529         ArraySet<String> categories = null;
   1530 
   1531         id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
   1532         activityComponent = ShortcutService.parseComponentNameAttribute(parser,
   1533                 ATTR_ACTIVITY);
   1534         title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
   1535         titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
   1536         titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME);
   1537         text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
   1538         textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
   1539         textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME);
   1540         disabledMessage = ShortcutService.parseStringAttribute(parser, ATTR_DISABLED_MESSAGE);
   1541         disabledMessageResId = ShortcutService.parseIntAttribute(parser,
   1542                 ATTR_DISABLED_MESSAGE_RES_ID);
   1543         disabledMessageResName = ShortcutService.parseStringAttribute(parser,
   1544                 ATTR_DISABLED_MESSAGE_RES_NAME);
   1545         disabledReason = ShortcutService.parseIntAttribute(parser, ATTR_DISABLED_REASON);
   1546         intentLegacy = ShortcutService.parseIntentAttributeNoDefault(parser, ATTR_INTENT_LEGACY);
   1547         rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
   1548         lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
   1549         flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
   1550         iconResId = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES_ID);
   1551         iconResName = ShortcutService.parseStringAttribute(parser, ATTR_ICON_RES_NAME);
   1552         bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
   1553 
   1554         final int outerDepth = parser.getDepth();
   1555         int type;
   1556         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
   1557                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
   1558             if (type != XmlPullParser.START_TAG) {
   1559                 continue;
   1560             }
   1561             final int depth = parser.getDepth();
   1562             final String tag = parser.getName();
   1563             if (ShortcutService.DEBUG_LOAD) {
   1564                 Slog.d(TAG, String.format("  depth=%d type=%d name=%s",
   1565                         depth, type, tag));
   1566             }
   1567             switch (tag) {
   1568                 case TAG_INTENT_EXTRAS_LEGACY:
   1569                     intentPersistableExtrasLegacy = PersistableBundle.restoreFromXml(parser);
   1570                     continue;
   1571                 case TAG_INTENT:
   1572                     intents.add(parseIntent(parser));
   1573                     continue;
   1574                 case TAG_EXTRAS:
   1575                     extras = PersistableBundle.restoreFromXml(parser);
   1576                     continue;
   1577                 case TAG_CATEGORIES:
   1578                     // This just contains string-array.
   1579                     continue;
   1580                 case TAG_STRING_ARRAY_XMLUTILS:
   1581                     if (NAME_CATEGORIES.equals(ShortcutService.parseStringAttribute(parser,
   1582                             ATTR_NAME_XMLUTILS))) {
   1583                         final String[] ar = XmlUtils.readThisStringArrayXml(
   1584                                 parser, TAG_STRING_ARRAY_XMLUTILS, null);
   1585                         categories = new ArraySet<>(ar.length);
   1586                         for (int i = 0; i < ar.length; i++) {
   1587                             categories.add(ar[i]);
   1588                         }
   1589                     }
   1590                     continue;
   1591             }
   1592             throw ShortcutService.throwForInvalidTag(depth, tag);
   1593         }
   1594 
   1595         if (intentLegacy != null) {
   1596             // For the legacy file format which supported only one intent per shortcut.
   1597             ShortcutInfo.setIntentExtras(intentLegacy, intentPersistableExtrasLegacy);
   1598             intents.clear();
   1599             intents.add(intentLegacy);
   1600         }
   1601 
   1602 
   1603         if ((disabledReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
   1604                 && ((flags & ShortcutInfo.FLAG_DISABLED) != 0)) {
   1605             // We didn't used to have the disabled reason, so if a shortcut is disabled
   1606             // and has no reason, we assume it was disabled by publisher.
   1607             disabledReason = ShortcutInfo.DISABLED_REASON_BY_APP;
   1608         }
   1609 
   1610         // All restored shortcuts are initially "shadow".
   1611         if (fromBackup) {
   1612             flags |= ShortcutInfo.FLAG_SHADOW;
   1613         }
   1614 
   1615         return new ShortcutInfo(
   1616                 userId, id, packageName, activityComponent, /* icon =*/ null,
   1617                 title, titleResId, titleResName, text, textResId, textResName,
   1618                 disabledMessage, disabledMessageResId, disabledMessageResName,
   1619                 categories,
   1620                 intents.toArray(new Intent[intents.size()]),
   1621                 rank, extras, lastChangedTimestamp, flags,
   1622                 iconResId, iconResName, bitmapPath, disabledReason);
   1623     }
   1624 
   1625     private static Intent parseIntent(XmlPullParser parser)
   1626             throws IOException, XmlPullParserException {
   1627 
   1628         Intent intent = ShortcutService.parseIntentAttribute(parser,
   1629                 ATTR_INTENT_NO_EXTRA);
   1630 
   1631         final int outerDepth = parser.getDepth();
   1632         int type;
   1633         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
   1634                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
   1635             if (type != XmlPullParser.START_TAG) {
   1636                 continue;
   1637             }
   1638             final int depth = parser.getDepth();
   1639             final String tag = parser.getName();
   1640             if (ShortcutService.DEBUG_LOAD) {
   1641                 Slog.d(TAG, String.format("  depth=%d type=%d name=%s",
   1642                         depth, type, tag));
   1643             }
   1644             switch (tag) {
   1645                 case TAG_EXTRAS:
   1646                     ShortcutInfo.setIntentExtras(intent,
   1647                             PersistableBundle.restoreFromXml(parser));
   1648                     continue;
   1649             }
   1650             throw ShortcutService.throwForInvalidTag(depth, tag);
   1651         }
   1652         return intent;
   1653     }
   1654 
   1655     @VisibleForTesting
   1656     List<ShortcutInfo> getAllShortcutsForTest() {
   1657         return new ArrayList<>(mShortcuts.values());
   1658     }
   1659 
   1660     @Override
   1661     public void verifyStates() {
   1662         super.verifyStates();
   1663 
   1664         boolean failed = false;
   1665 
   1666         final ShortcutService s = mShortcutUser.mService;
   1667 
   1668         final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
   1669                 sortShortcutsToActivities();
   1670 
   1671         // Make sure each activity won't have more than max shortcuts.
   1672         for (int outer = all.size() - 1; outer >= 0; outer--) {
   1673             final ArrayList<ShortcutInfo> list = all.valueAt(outer);
   1674             if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
   1675                 failed = true;
   1676                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
   1677                         + " has " + all.valueAt(outer).size() + " shortcuts.");
   1678             }
   1679 
   1680             // Sort by rank.
   1681             Collections.sort(list, (a, b) -> Integer.compare(a.getRank(), b.getRank()));
   1682 
   1683             // Split into two arrays for each kind.
   1684             final ArrayList<ShortcutInfo> dynamicList = new ArrayList<>(list);
   1685             dynamicList.removeIf((si) -> !si.isDynamic());
   1686 
   1687             final ArrayList<ShortcutInfo> manifestList = new ArrayList<>(list);
   1688             dynamicList.removeIf((si) -> !si.isManifestShortcut());
   1689 
   1690             verifyRanksSequential(dynamicList);
   1691             verifyRanksSequential(manifestList);
   1692         }
   1693 
   1694         // Verify each shortcut's status.
   1695         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
   1696             final ShortcutInfo si = mShortcuts.valueAt(i);
   1697             if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned())) {
   1698                 failed = true;
   1699                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1700                         + " is not manifest, dynamic or pinned.");
   1701             }
   1702             if (si.isDeclaredInManifest() && si.isDynamic()) {
   1703                 failed = true;
   1704                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1705                         + " is both dynamic and manifest at the same time.");
   1706             }
   1707             if (si.getActivity() == null && !si.isFloating()) {
   1708                 failed = true;
   1709                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1710                         + " has null activity, but not floating.");
   1711             }
   1712             if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
   1713                 failed = true;
   1714                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1715                         + " is not floating, but is disabled.");
   1716             }
   1717             if (si.isFloating() && si.getRank() != 0) {
   1718                 failed = true;
   1719                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1720                         + " is floating, but has rank=" + si.getRank());
   1721             }
   1722             if (si.getIcon() != null) {
   1723                 failed = true;
   1724                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1725                         + " still has an icon");
   1726             }
   1727             if (si.hasAdaptiveBitmap() && !si.hasIconFile()) {
   1728                 failed = true;
   1729                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1730                     + " has adaptive bitmap but was not saved to a file.");
   1731             }
   1732             if (si.hasIconFile() && si.hasIconResource()) {
   1733                 failed = true;
   1734                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1735                         + " has both resource and bitmap icons");
   1736             }
   1737             if (si.isEnabled()
   1738                     != (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
   1739                 failed = true;
   1740                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1741                         + " isEnabled() and getDisabledReason() disagree: "
   1742                         + si.isEnabled() + " vs " + si.getDisabledReason());
   1743             }
   1744             if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
   1745                     && (getPackageInfo().getBackupSourceVersionCode()
   1746                     == ShortcutInfo.VERSION_CODE_UNKNOWN)) {
   1747                 failed = true;
   1748                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1749                         + " RESTORED_VERSION_LOWER with no backup source version code.");
   1750             }
   1751             if (s.isDummyMainActivity(si.getActivity())) {
   1752                 failed = true;
   1753                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1754                         + " has a dummy target activity");
   1755             }
   1756         }
   1757 
   1758         if (failed) {
   1759             throw new IllegalStateException("See logcat for errors");
   1760         }
   1761     }
   1762 
   1763     private boolean verifyRanksSequential(List<ShortcutInfo> list) {
   1764         boolean failed = false;
   1765 
   1766         for (int i = 0; i < list.size(); i++) {
   1767             final ShortcutInfo si = list.get(i);
   1768             if (si.getRank() != i) {
   1769                 failed = true;
   1770                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
   1771                         + " rank=" + si.getRank() + " but expected to be "+ i);
   1772             }
   1773         }
   1774         return failed;
   1775     }
   1776 }
   1777