blob: eced165bf25753519dfb907e48e416c7f298026a [file] [log] [blame]
Makoto Onuki31459242016-03-22 11:12:18 -07001/*
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.NonNull;
19import android.annotation.Nullable;
Makoto Onukiabe84422016-04-07 09:41:19 -070020import android.annotation.UserIdInt;
Mehdi Alizadehebb4b602019-02-05 15:52:18 -080021import android.app.Person;
Makoto Onuki31459242016-03-22 11:12:18 -070022import android.content.ComponentName;
23import android.content.Intent;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080024import android.content.IntentFilter;
Felipe Leme90205ef2019-03-05 09:59:52 -080025import android.content.LocusId;
Makoto Onuki22fcc682016-05-17 14:52:19 -070026import android.content.pm.PackageInfo;
Makoto Onuki31459242016-03-22 11:12:18 -070027import android.content.pm.ShortcutInfo;
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -080028import android.content.pm.ShortcutManager;
Makoto Onuki157b1622016-06-02 16:13:10 -070029import android.content.res.Resources;
Makoto Onuki31459242016-03-22 11:12:18 -070030import android.os.PersistableBundle;
31import android.text.format.Formatter;
32import android.util.ArrayMap;
33import android.util.ArraySet;
Makoto Onuki7001a612016-05-27 13:24:28 -070034import android.util.Log;
Makoto Onuki31459242016-03-22 11:12:18 -070035import android.util.Slog;
36
Makoto Onuki2e210c42016-03-30 08:30:36 -070037import com.android.internal.annotations.VisibleForTesting;
Mehdi Alizadehebb4b602019-02-05 15:52:18 -080038import com.android.internal.util.ArrayUtils;
Makoto Onuki22fcc682016-05-17 14:52:19 -070039import com.android.internal.util.Preconditions;
Makoto Onukib6d35232016-04-04 15:57:17 -070040import com.android.internal.util.XmlUtils;
Makoto Onuki20b82212017-10-04 15:03:50 -070041import com.android.server.pm.ShortcutService.DumpFilter;
Makoto Onuki7001a612016-05-27 13:24:28 -070042import com.android.server.pm.ShortcutService.ShortcutOperation;
Makoto Onuki4e6cef42016-07-13 16:14:01 -070043import com.android.server.pm.ShortcutService.Stats;
Makoto Onuki2e210c42016-03-30 08:30:36 -070044
Makoto Onuki76269922016-07-15 14:58:54 -070045import org.json.JSONException;
46import org.json.JSONObject;
Makoto Onuki31459242016-03-22 11:12:18 -070047import org.xmlpull.v1.XmlPullParser;
48import org.xmlpull.v1.XmlPullParserException;
49import org.xmlpull.v1.XmlSerializer;
50
51import java.io.File;
52import java.io.IOException;
53import java.io.PrintWriter;
54import java.util.ArrayList;
Makoto Onuki7001a612016-05-27 13:24:28 -070055import java.util.Collections;
56import java.util.Comparator;
Makoto Onuki31459242016-03-22 11:12:18 -070057import java.util.List;
Makoto Onukibe73a802016-04-15 14:46:35 -070058import java.util.Set;
Makoto Onuki31459242016-03-22 11:12:18 -070059import java.util.function.Predicate;
60
61/**
62 * Package information used by {@link ShortcutService}.
Makoto Onuki22fcc682016-05-17 14:52:19 -070063 * User information used by {@link ShortcutService}.
64 *
65 * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
Makoto Onuki31459242016-03-22 11:12:18 -070066 */
Makoto Onuki9da23fc2016-03-29 11:14:42 -070067class ShortcutPackage extends ShortcutPackageItem {
Makoto Onuki31459242016-03-22 11:12:18 -070068 private static final String TAG = ShortcutService.TAG;
Makoto Onuki9e1f5592016-06-08 12:30:23 -070069 private static final String TAG_VERIFY = ShortcutService.TAG + ".verify";
Makoto Onuki31459242016-03-22 11:12:18 -070070
71 static final String TAG_ROOT = "package";
Makoto Onuki440a1ea2016-07-20 14:21:18 -070072 private static final String TAG_INTENT_EXTRAS_LEGACY = "intent-extras";
73 private static final String TAG_INTENT = "intent";
Makoto Onuki31459242016-03-22 11:12:18 -070074 private static final String TAG_EXTRAS = "extras";
75 private static final String TAG_SHORTCUT = "shortcut";
Makoto Onukib6d35232016-04-04 15:57:17 -070076 private static final String TAG_CATEGORIES = "categories";
Mehdi Alizadehebb4b602019-02-05 15:52:18 -080077 private static final String TAG_PERSON = "person";
Makoto Onuki31459242016-03-22 11:12:18 -070078
79 private static final String ATTR_NAME = "name";
Makoto Onuki31459242016-03-22 11:12:18 -070080 private static final String ATTR_CALL_COUNT = "call-count";
81 private static final String ATTR_LAST_RESET = "last-reset";
82 private static final String ATTR_ID = "id";
83 private static final String ATTR_ACTIVITY = "activity";
84 private static final String ATTR_TITLE = "title";
Makoto Onuki20c95f82016-05-11 16:51:01 -070085 private static final String ATTR_TITLE_RES_ID = "titleid";
Makoto Onuki157b1622016-06-02 16:13:10 -070086 private static final String ATTR_TITLE_RES_NAME = "titlename";
Makoto Onukie3ae7ec2016-03-29 15:45:25 -070087 private static final String ATTR_TEXT = "text";
Makoto Onuki20c95f82016-05-11 16:51:01 -070088 private static final String ATTR_TEXT_RES_ID = "textid";
Makoto Onuki157b1622016-06-02 16:13:10 -070089 private static final String ATTR_TEXT_RES_NAME = "textname";
Makoto Onuki20c95f82016-05-11 16:51:01 -070090 private static final String ATTR_DISABLED_MESSAGE = "dmessage";
91 private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
Makoto Onuki157b1622016-06-02 16:13:10 -070092 private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename";
Makoto Onukia4f89b12017-10-05 10:37:55 -070093 private static final String ATTR_DISABLED_REASON = "disabled-reason";
Makoto Onuki440a1ea2016-07-20 14:21:18 -070094 private static final String ATTR_INTENT_LEGACY = "intent";
95 private static final String ATTR_INTENT_NO_EXTRA = "intent-base";
Makoto Onuki20c95f82016-05-11 16:51:01 -070096 private static final String ATTR_RANK = "rank";
Makoto Onuki31459242016-03-22 11:12:18 -070097 private static final String ATTR_TIMESTAMP = "timestamp";
98 private static final String ATTR_FLAGS = "flags";
Makoto Onuki157b1622016-06-02 16:13:10 -070099 private static final String ATTR_ICON_RES_ID = "icon-res";
100 private static final String ATTR_ICON_RES_NAME = "icon-resname";
Makoto Onuki31459242016-03-22 11:12:18 -0700101 private static final String ATTR_BITMAP_PATH = "bitmap-path";
102
Mehdi Alizadehebb4b602019-02-05 15:52:18 -0800103 private static final String ATTR_PERSON_NAME = "name";
104 private static final String ATTR_PERSON_URI = "uri";
105 private static final String ATTR_PERSON_KEY = "key";
106 private static final String ATTR_PERSON_IS_BOT = "is-bot";
107 private static final String ATTR_PERSON_IS_IMPORTANT = "is-important";
108
Makoto Onukib6d35232016-04-04 15:57:17 -0700109 private static final String NAME_CATEGORIES = "categories";
110
111 private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array";
112 private static final String ATTR_NAME_XMLUTILS = "name";
113
Makoto Onuki76269922016-07-15 14:58:54 -0700114 private static final String KEY_DYNAMIC = "dynamic";
115 private static final String KEY_MANIFEST = "manifest";
116 private static final String KEY_PINNED = "pinned";
117 private static final String KEY_BITMAPS = "bitmaps";
118 private static final String KEY_BITMAP_BYTES = "bitmapBytes";
119
Makoto Onuki31459242016-03-22 11:12:18 -0700120 /**
121 * All the shortcuts from the package, keyed on IDs.
122 */
123 final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
124
125 /**
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800126 * All the share targets from the package
127 */
128 private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);
129
130 /**
Makoto Onuki31459242016-03-22 11:12:18 -0700131 * # of times the package has called rate-limited APIs.
132 */
133 private int mApiCallCount;
134
135 /**
136 * When {@link #mApiCallCount} was reset last time.
137 */
138 private long mLastResetTime;
139
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700140 private final int mPackageUid;
141
142 private long mLastKnownForegroundElapsedTime;
143
Makoto Onukic51b2872016-05-04 15:24:50 -0700144 private ShortcutPackage(ShortcutUser shortcutUser,
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700145 int packageUserId, String packageName, ShortcutPackageInfo spi) {
146 super(shortcutUser, packageUserId, packageName,
147 spi != null ? spi : ShortcutPackageInfo.newEmpty());
148
Makoto Onukic51b2872016-05-04 15:24:50 -0700149 mPackageUid = shortcutUser.mService.injectGetPackageUid(packageName, packageUserId);
Makoto Onuki31459242016-03-22 11:12:18 -0700150 }
151
Makoto Onukic51b2872016-05-04 15:24:50 -0700152 public ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName) {
153 this(shortcutUser, packageUserId, packageName, null);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700154 }
155
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700156 @Override
157 public int getOwnerUserId() {
158 // For packages, always owner user == package user.
159 return getPackageUserId();
Makoto Onuki0acbb142016-03-22 17:02:57 -0700160 }
161
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700162 public int getPackageUid() {
163 return mPackageUid;
164 }
165
Makoto Onuki157b1622016-06-02 16:13:10 -0700166 @Nullable
167 public Resources getPackageResources() {
168 return mShortcutUser.mService.injectGetResourcesForApplicationAsUser(
169 getPackageName(), getPackageUserId());
170 }
171
Makoto Onuki50a320e2017-05-31 14:38:42 -0700172 public int getShortcutCount() {
173 return mShortcuts.size();
174 }
175
Makoto Onuki2e210c42016-03-30 08:30:36 -0700176 @Override
Makoto Onukia4f89b12017-10-05 10:37:55 -0700177 protected boolean canRestoreAnyVersion() {
178 return false;
Makoto Onuki2e210c42016-03-30 08:30:36 -0700179 }
180
181 @Override
Makoto Onukia4f89b12017-10-05 10:37:55 -0700182 protected void onRestored(int restoreBlockReason) {
183 // Shortcuts have been restored.
184 // - Unshadow all shortcuts.
185 // - Set disabled reason.
186 // - Disable if needed.
187 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
188 ShortcutInfo si = mShortcuts.valueAt(i);
189 si.clearFlags(ShortcutInfo.FLAG_SHADOW);
190
191 si.setDisabledReason(restoreBlockReason);
192 if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
193 si.addFlags(ShortcutInfo.FLAG_DISABLED);
194 }
195 }
Makoto Onuki2e210c42016-03-30 08:30:36 -0700196 // Because some launchers may not have been restored (e.g. allowBackup=false),
197 // we need to re-calculate the pinned shortcuts.
Makoto Onukic51b2872016-05-04 15:24:50 -0700198 refreshPinnedFlags();
Makoto Onuki2e210c42016-03-30 08:30:36 -0700199 }
200
Makoto Onukid99c6f02016-03-28 11:02:54 -0700201 /**
202 * Note this does *not* provide a correct view to the calling launcher.
203 */
Makoto Onuki31459242016-03-22 11:12:18 -0700204 @Nullable
205 public ShortcutInfo findShortcutById(String id) {
206 return mShortcuts.get(id);
207 }
208
Makoto Onukia4f89b12017-10-05 10:37:55 -0700209 public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
210 ShortcutInfo si = findShortcutById(id);
211 return si != null && !si.isVisibleToPublisher();
212 }
213
214 public boolean isShortcutExistsAndVisibleToPublisher(String id) {
215 ShortcutInfo si = findShortcutById(id);
216 return si != null && si.isVisibleToPublisher();
217 }
218
219 private void ensureNotImmutable(@Nullable ShortcutInfo shortcut, boolean ignoreInvisible) {
220 if (shortcut != null && shortcut.isImmutable()
221 && (!ignoreInvisible || shortcut.isVisibleToPublisher())) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700222 throw new IllegalArgumentException(
223 "Manifest shortcut ID=" + shortcut.getId()
224 + " may not be manipulated via APIs");
225 }
226 }
227
Makoto Onukia4f89b12017-10-05 10:37:55 -0700228 public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
229 ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700230 }
231
Makoto Onukia4f89b12017-10-05 10:37:55 -0700232 public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
233 boolean ignoreInvisible) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700234 for (int i = shortcutIds.size() - 1; i >= 0; i--) {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700235 ensureNotImmutable(shortcutIds.get(i), ignoreInvisible);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700236 }
237 }
238
Makoto Onukia4f89b12017-10-05 10:37:55 -0700239 public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts,
240 boolean ignoreInvisible) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700241 for (int i = shortcuts.size() - 1; i >= 0; i--) {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700242 ensureNotImmutable(shortcuts.get(i).getId(), ignoreInvisible);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700243 }
244 }
245
Makoto Onukia4f89b12017-10-05 10:37:55 -0700246 /**
247 * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
248 */
249 private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
Makoto Onuki31459242016-03-22 11:12:18 -0700250 final ShortcutInfo shortcut = mShortcuts.remove(id);
251 if (shortcut != null) {
Makoto Onuki475c3652017-05-08 14:29:03 -0700252 mShortcutUser.mService.removeIconLocked(shortcut);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700253 shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
Makoto Onuki99302b52017-03-29 12:42:26 -0700254 | ShortcutInfo.FLAG_MANIFEST);
Makoto Onuki31459242016-03-22 11:12:18 -0700255 }
256 return shortcut;
257 }
258
Makoto Onukia4f89b12017-10-05 10:37:55 -0700259 /**
260 * Force replace a shortcut. If there's already a shortcut with the same ID, it'll be removed,
261 * even if it's invisible.
262 */
263 private void forceReplaceShortcutInner(@NonNull ShortcutInfo newShortcut) {
Makoto Onuki157b1622016-06-02 16:13:10 -0700264 final ShortcutService s = mShortcutUser.mService;
265
Makoto Onukia4f89b12017-10-05 10:37:55 -0700266 forceDeleteShortcutInner(newShortcut.getId());
Makoto Onuki157b1622016-06-02 16:13:10 -0700267
268 // Extract Icon and update the icon res ID and the bitmap path.
Makoto Onuki475c3652017-05-08 14:29:03 -0700269 s.saveIconAndFixUpShortcutLocked(newShortcut);
Makoto Onuki157b1622016-06-02 16:13:10 -0700270 s.fixUpShortcutResourceNamesAndValues(newShortcut);
Makoto Onuki31459242016-03-22 11:12:18 -0700271 mShortcuts.put(newShortcut.getId(), newShortcut);
272 }
273
274 /**
Makoto Onukia4f89b12017-10-05 10:37:55 -0700275 * Add a shortcut. If there's already a one with the same ID, it'll be removed, even if it's
276 * invisible.
Makoto Onuki31459242016-03-22 11:12:18 -0700277 *
278 * It checks the max number of dynamic shortcuts.
279 */
Makoto Onukia4f89b12017-10-05 10:37:55 -0700280 public void addOrReplaceDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
Makoto Onuki39686e82016-04-13 18:03:00 -0700281
Makoto Onuki22fcc682016-05-17 14:52:19 -0700282 Preconditions.checkArgument(newShortcut.isEnabled(),
283 "add/setDynamicShortcuts() cannot publish disabled shortcuts");
284
Makoto Onuki99302b52017-03-29 12:42:26 -0700285 newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
Makoto Onuki31459242016-03-22 11:12:18 -0700286
287 final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
288
289 final boolean wasPinned;
Makoto Onuki31459242016-03-22 11:12:18 -0700290
291 if (oldShortcut == null) {
292 wasPinned = false;
Makoto Onuki31459242016-03-22 11:12:18 -0700293 } else {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700294 // It's an update case.
295 // Make sure the target is updatable. (i.e. should be mutable.)
Makoto Onukia4f89b12017-10-05 10:37:55 -0700296 oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700297
Makoto Onuki31459242016-03-22 11:12:18 -0700298 wasPinned = oldShortcut.isPinned();
Makoto Onuki31459242016-03-22 11:12:18 -0700299 }
300
Makoto Onuki157b1622016-06-02 16:13:10 -0700301 // If it was originally pinned, the new one should be pinned too.
Makoto Onuki31459242016-03-22 11:12:18 -0700302 if (wasPinned) {
303 newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
304 }
305
Makoto Onukia4f89b12017-10-05 10:37:55 -0700306 forceReplaceShortcutInner(newShortcut);
Makoto Onuki31459242016-03-22 11:12:18 -0700307 }
308
309 /**
310 * Remove all shortcuts that aren't pinned nor dynamic.
311 */
Makoto Onukic51b2872016-05-04 15:24:50 -0700312 private void removeOrphans() {
Makoto Onuki31459242016-03-22 11:12:18 -0700313 ArrayList<String> removeList = null; // Lazily initialize.
314
315 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
316 final ShortcutInfo si = mShortcuts.valueAt(i);
317
Makoto Onuki22fcc682016-05-17 14:52:19 -0700318 if (si.isAlive()) continue;
Makoto Onuki31459242016-03-22 11:12:18 -0700319
320 if (removeList == null) {
321 removeList = new ArrayList<>();
322 }
323 removeList.add(si.getId());
324 }
325 if (removeList != null) {
326 for (int i = removeList.size() - 1; i >= 0; i--) {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700327 forceDeleteShortcutInner(removeList.get(i));
Makoto Onuki31459242016-03-22 11:12:18 -0700328 }
329 }
330 }
331
332 /**
333 * Remove all dynamic shortcuts.
334 */
Makoto Onukia4f89b12017-10-05 10:37:55 -0700335 public void deleteAllDynamicShortcuts(boolean ignoreInvisible) {
Makoto Onuki9e1f5592016-06-08 12:30:23 -0700336 final long now = mShortcutUser.mService.injectCurrentTimeMillis();
337
Makoto Onuki22fcc682016-05-17 14:52:19 -0700338 boolean changed = false;
Makoto Onuki31459242016-03-22 11:12:18 -0700339 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700340 final ShortcutInfo si = mShortcuts.valueAt(i);
Makoto Onukia4f89b12017-10-05 10:37:55 -0700341 if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700342 changed = true;
Makoto Onuki9e1f5592016-06-08 12:30:23 -0700343
344 si.setTimestamp(now);
Makoto Onuki99302b52017-03-29 12:42:26 -0700345 si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
Makoto Onuki9e1f5592016-06-08 12:30:23 -0700346 si.setRank(0); // It may still be pinned, so clear the rank.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700347 }
Makoto Onuki31459242016-03-22 11:12:18 -0700348 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700349 if (changed) {
350 removeOrphans();
351 }
Makoto Onuki31459242016-03-22 11:12:18 -0700352 }
353
354 /**
Makoto Onuki7001a612016-05-27 13:24:28 -0700355 * Remove a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
356 * is pinned, it'll remain as a pinned shortcut, and is still enabled.
Makoto Onukib08790c2016-06-23 14:05:46 -0700357 *
358 * @return true if it's actually removed because it wasn't pinned, or false if it's still
359 * pinned.
Makoto Onuki31459242016-03-22 11:12:18 -0700360 */
Makoto Onukia4f89b12017-10-05 10:37:55 -0700361 public boolean deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
Makoto Onukib08790c2016-06-23 14:05:46 -0700362 final ShortcutInfo removed = deleteOrDisableWithId(
Makoto Onukia4f89b12017-10-05 10:37:55 -0700363 shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible,
364 ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
Makoto Onukib08790c2016-06-23 14:05:46 -0700365 return removed == null;
366 }
367
368 /**
369 * Disable a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
370 * is pinned, it'll remain as a pinned shortcut, but will be disabled.
371 *
372 * @return true if it's actually removed because it wasn't pinned, or false if it's still
373 * pinned.
374 */
Makoto Onukia4f89b12017-10-05 10:37:55 -0700375 private boolean disableDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible,
376 int disabledReason) {
Makoto Onukib08790c2016-06-23 14:05:46 -0700377 final ShortcutInfo disabled = deleteOrDisableWithId(
Makoto Onukia4f89b12017-10-05 10:37:55 -0700378 shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false, ignoreInvisible,
379 disabledReason);
Makoto Onukib08790c2016-06-23 14:05:46 -0700380 return disabled == null;
Makoto Onuki22fcc682016-05-17 14:52:19 -0700381 }
382
Makoto Onuki7001a612016-05-27 13:24:28 -0700383 /**
384 * Disable a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
385 * is pinned, it'll remain as a pinned shortcut but will be disabled.
386 */
Makoto Onuki22fcc682016-05-17 14:52:19 -0700387 public void disableWithId(@NonNull String shortcutId, String disabledMessage,
Makoto Onukia4f89b12017-10-05 10:37:55 -0700388 int disabledMessageResId, boolean overrideImmutable, boolean ignoreInvisible,
389 int disabledReason) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700390 final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
Makoto Onukia4f89b12017-10-05 10:37:55 -0700391 overrideImmutable, ignoreInvisible, disabledReason);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700392
393 if (disabled != null) {
394 if (disabledMessage != null) {
395 disabled.setDisabledMessage(disabledMessage);
396 } else if (disabledMessageResId != 0) {
397 disabled.setDisabledMessageResId(disabledMessageResId);
Makoto Onuki157b1622016-06-02 16:13:10 -0700398
399 mShortcutUser.mService.fixUpShortcutResourceNamesAndValues(disabled);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700400 }
401 }
402 }
403
404 @Nullable
405 private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
Makoto Onukia4f89b12017-10-05 10:37:55 -0700406 boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) {
407 Preconditions.checkState(
408 (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
409 "disable and disabledReason disagree: " + disable + " vs " + disabledReason);
Makoto Onuki31459242016-03-22 11:12:18 -0700410 final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
411
Makoto Onukia4f89b12017-10-05 10:37:55 -0700412 if (oldShortcut == null || !oldShortcut.isEnabled()
413 && (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
Makoto Onuki22fcc682016-05-17 14:52:19 -0700414 return null; // Doesn't exist or already disabled.
Makoto Onuki31459242016-03-22 11:12:18 -0700415 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700416 if (!overrideImmutable) {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700417 ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true);
Makoto Onuki31459242016-03-22 11:12:18 -0700418 }
419 if (oldShortcut.isPinned()) {
Makoto Onuki9e1f5592016-06-08 12:30:23 -0700420
421 oldShortcut.setRank(0);
Makoto Onuki99302b52017-03-29 12:42:26 -0700422 oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700423 if (disable) {
424 oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
Makoto Onukia4f89b12017-10-05 10:37:55 -0700425 // Do not overwrite the disabled reason if one is alreay set.
426 if (oldShortcut.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
427 oldShortcut.setDisabledReason(disabledReason);
428 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700429 }
Makoto Onuki9e1f5592016-06-08 12:30:23 -0700430 oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
431
Makoto Onuki255461f2017-01-10 11:47:25 -0800432 // See ShortcutRequestPinProcessor.directPinShortcut().
433 if (mShortcutUser.mService.isDummyMainActivity(oldShortcut.getActivity())) {
434 oldShortcut.setActivity(null);
435 }
436
Makoto Onuki22fcc682016-05-17 14:52:19 -0700437 return oldShortcut;
Makoto Onuki31459242016-03-22 11:12:18 -0700438 } else {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700439 forceDeleteShortcutInner(shortcutId);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700440 return null;
441 }
442 }
443
444 public void enableWithId(@NonNull String shortcutId) {
445 final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
446 if (shortcut != null) {
Makoto Onukia4f89b12017-10-05 10:37:55 -0700447 ensureNotImmutable(shortcut, /*ignoreInvisible=*/ true);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700448 shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
Makoto Onukia4f89b12017-10-05 10:37:55 -0700449 shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
Makoto Onuki31459242016-03-22 11:12:18 -0700450 }
451 }
452
Makoto Onukia4f89b12017-10-05 10:37:55 -0700453 public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
454 final ShortcutInfo source = mShortcuts.get(shortcut.getId());
455 Preconditions.checkNotNull(source);
456
457 mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
458
459 shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
460
461 forceReplaceShortcutInner(shortcut);
462
463 adjustRanks();
464 }
465
Makoto Onuki31459242016-03-22 11:12:18 -0700466 /**
467 * Called after a launcher updates the pinned set. For each shortcut in this package,
468 * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it.
469 *
470 * <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
471 */
Makoto Onukic51b2872016-05-04 15:24:50 -0700472 public void refreshPinnedFlags() {
Makoto Onuki31459242016-03-22 11:12:18 -0700473 // First, un-pin all shortcuts
474 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
475 mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
476 }
477
478 // Then, for the pinned set for each launcher, set the pin flag one by one.
Makoto Onuki47ed1712017-12-20 14:40:20 +0900479 mShortcutUser.forAllLaunchers(launcherShortcuts -> {
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700480 final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
Makoto Onuki2e210c42016-03-30 08:30:36 -0700481 getPackageName(), getPackageUserId());
Makoto Onuki31459242016-03-22 11:12:18 -0700482
483 if (pinned == null || pinned.size() == 0) {
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700484 return;
Makoto Onuki31459242016-03-22 11:12:18 -0700485 }
486 for (int i = pinned.size() - 1; i >= 0; i--) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700487 final String id = pinned.valueAt(i);
488 final ShortcutInfo si = mShortcuts.get(id);
Makoto Onuki31459242016-03-22 11:12:18 -0700489 if (si == null) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700490 // This happens if a launcher pinned shortcuts from this package, then backup&
491 // restored, but this package doesn't allow backing up.
492 // In that case the launcher ends up having a dangling pinned shortcuts.
493 // That's fine, when the launcher is restored, we'll fix it.
494 continue;
Makoto Onuki31459242016-03-22 11:12:18 -0700495 }
Makoto Onuki2e210c42016-03-30 08:30:36 -0700496 si.addFlags(ShortcutInfo.FLAG_PINNED);
Makoto Onuki31459242016-03-22 11:12:18 -0700497 }
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700498 });
Makoto Onuki31459242016-03-22 11:12:18 -0700499
500 // Lastly, remove the ones that are no longer pinned nor dynamic.
Makoto Onukic51b2872016-05-04 15:24:50 -0700501 removeOrphans();
Makoto Onuki31459242016-03-22 11:12:18 -0700502 }
503
504 /**
505 * Number of calls that the caller has made, since the last reset.
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700506 *
507 * <p>This takes care of the resetting the counter for foreground apps as well as after
508 * locale changes.
Makoto Onuki31459242016-03-22 11:12:18 -0700509 */
Makoto Onuki7d0fa812018-02-21 11:24:43 -0800510 public int getApiCallCount(boolean unlimited) {
Makoto Onukic51b2872016-05-04 15:24:50 -0700511 final ShortcutService s = mShortcutUser.mService;
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700512
513 // Reset the counter if:
514 // - the package is in foreground now.
515 // - the package is *not* in foreground now, but was in foreground at some point
516 // since the previous time it had been.
517 if (s.isUidForegroundLocked(mPackageUid)
Makoto Onuki7d0fa812018-02-21 11:24:43 -0800518 || (mLastKnownForegroundElapsedTime
519 < s.getUidLastForegroundElapsedTimeLocked(mPackageUid))
520 || unlimited) {
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700521 mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
Makoto Onukic51b2872016-05-04 15:24:50 -0700522 resetRateLimiting();
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700523 }
524
525 // Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount,
526 // but we just can't return 0 at this point, because we may have to update
527 // mLastResetTime.
528
Makoto Onuki31459242016-03-22 11:12:18 -0700529 final long last = s.getLastResetTimeLocked();
530
531 final long now = s.injectCurrentTimeMillis();
532 if (ShortcutService.isClockValid(now) && mLastResetTime > now) {
533 Slog.w(TAG, "Clock rewound");
534 // Clock rewound.
535 mLastResetTime = now;
536 mApiCallCount = 0;
537 return mApiCallCount;
538 }
539
540 // If not reset yet, then reset.
541 if (mLastResetTime < last) {
542 if (ShortcutService.DEBUG) {
Makoto Onukic51b2872016-05-04 15:24:50 -0700543 Slog.d(TAG, String.format("%s: last reset=%d, now=%d, last=%d: resetting",
544 getPackageName(), mLastResetTime, now, last));
Makoto Onuki31459242016-03-22 11:12:18 -0700545 }
546 mApiCallCount = 0;
547 mLastResetTime = last;
548 }
549 return mApiCallCount;
550 }
551
552 /**
553 * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
554 * and return true. Otherwise just return false.
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700555 *
556 * <p>This takes care of the resetting the counter for foreground apps as well as after
557 * locale changes, which is done internally by {@link #getApiCallCount}.
Makoto Onuki31459242016-03-22 11:12:18 -0700558 */
Makoto Onuki7d0fa812018-02-21 11:24:43 -0800559 public boolean tryApiCall(boolean unlimited) {
Makoto Onukic51b2872016-05-04 15:24:50 -0700560 final ShortcutService s = mShortcutUser.mService;
561
Makoto Onuki7d0fa812018-02-21 11:24:43 -0800562 if (getApiCallCount(unlimited) >= s.mMaxUpdatesPerInterval) {
Makoto Onuki31459242016-03-22 11:12:18 -0700563 return false;
564 }
565 mApiCallCount++;
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700566 s.scheduleSaveUser(getOwnerUserId());
Makoto Onuki31459242016-03-22 11:12:18 -0700567 return true;
568 }
569
Makoto Onukic51b2872016-05-04 15:24:50 -0700570 public void resetRateLimiting() {
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700571 if (ShortcutService.DEBUG) {
572 Slog.d(TAG, "resetRateLimiting: " + getPackageName());
573 }
574 if (mApiCallCount > 0) {
575 mApiCallCount = 0;
Makoto Onukic51b2872016-05-04 15:24:50 -0700576 mShortcutUser.mService.scheduleSaveUser(getOwnerUserId());
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700577 }
578 }
579
580 public void resetRateLimitingForCommandLineNoSaving() {
Makoto Onuki31459242016-03-22 11:12:18 -0700581 mApiCallCount = 0;
582 mLastResetTime = 0;
583 }
584
585 /**
586 * Find all shortcuts that match {@code query}.
587 */
Makoto Onukic51b2872016-05-04 15:24:50 -0700588 public void findAll(@NonNull List<ShortcutInfo> result,
Makoto Onukid99c6f02016-03-28 11:02:54 -0700589 @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
Makoto Onuki634cecb2017-10-13 17:10:48 -0700590 findAll(result, query, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
Makoto Onukid99c6f02016-03-28 11:02:54 -0700591 }
592
593 /**
594 * Find all shortcuts that match {@code query}.
595 *
596 * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned
597 * by the calling launcher will not be included in the result, and also "isPinned" will be
598 * adjusted for the caller too.
599 */
Makoto Onukic51b2872016-05-04 15:24:50 -0700600 public void findAll(@NonNull List<ShortcutInfo> result,
Makoto Onuki31459242016-03-22 11:12:18 -0700601 @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
Makoto Onuki634cecb2017-10-13 17:10:48 -0700602 @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700603 if (getPackageInfo().isShadow()) {
604 // Restored and the app not installed yet, so don't return any.
605 return;
606 }
Makoto Onuki31459242016-03-22 11:12:18 -0700607
Makoto Onukic51b2872016-05-04 15:24:50 -0700608 final ShortcutService s = mShortcutUser.mService;
609
Makoto Onuki31459242016-03-22 11:12:18 -0700610 // Set of pinned shortcuts by the calling launcher.
611 final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
Makoto Onuki2e210c42016-03-30 08:30:36 -0700612 : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
613 .getPinnedShortcutIds(getPackageName(), getPackageUserId());
Makoto Onuki31459242016-03-22 11:12:18 -0700614
615 for (int i = 0; i < mShortcuts.size(); i++) {
616 final ShortcutInfo si = mShortcuts.valueAt(i);
617
Makoto Onuki22fcc682016-05-17 14:52:19 -0700618 // Need to adjust PINNED flag depending on the caller.
619 // Basically if the caller is a launcher (callingLauncher != null) and the launcher
620 // isn't pinning it, then we need to clear PINNED for this caller.
Makoto Onuki31459242016-03-22 11:12:18 -0700621 final boolean isPinnedByCaller = (callingLauncher == null)
622 || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
Makoto Onuki22fcc682016-05-17 14:52:19 -0700623
Makoto Onuki634cecb2017-10-13 17:10:48 -0700624 if (!getPinnedByAnyLauncher) {
625 if (si.isFloating()) {
626 if (!isPinnedByCaller) {
627 continue;
628 }
Makoto Onuki31459242016-03-22 11:12:18 -0700629 }
630 }
631 final ShortcutInfo clone = si.clone(cloneFlag);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700632
Makoto Onuki31459242016-03-22 11:12:18 -0700633 // Fix up isPinned for the caller. Note we need to do it before the "test" callback,
634 // since it may check isPinned.
Makoto Onuki35559d62017-11-06 16:26:32 -0800635 // However, if getPinnedByAnyLauncher is set, we do it after the test.
636 if (!getPinnedByAnyLauncher) {
637 if (!isPinnedByCaller) {
638 clone.clearFlags(ShortcutInfo.FLAG_PINNED);
639 }
Makoto Onuki31459242016-03-22 11:12:18 -0700640 }
641 if (query == null || query.test(clone)) {
Makoto Onuki35559d62017-11-06 16:26:32 -0800642 if (!isPinnedByCaller) {
643 clone.clearFlags(ShortcutInfo.FLAG_PINNED);
644 }
Makoto Onuki31459242016-03-22 11:12:18 -0700645 result.add(clone);
646 }
647 }
648 }
649
650 public void resetThrottling() {
651 mApiCallCount = 0;
652 }
653
Makoto Onuki39686e82016-04-13 18:03:00 -0700654 /**
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800655 * Returns a list of ShortcutInfos that match the given intent filter and the category of
656 * available ShareTarget definitions in this package.
657 */
658 public List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
659 @NonNull IntentFilter filter) {
660 final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
661 for (int i = 0; i < mShareTargets.size(); i++) {
662 final ShareTargetInfo target = mShareTargets.get(i);
663 for (ShareTargetInfo.TargetData data : target.mTargetData) {
664 if (filter.hasDataType(data.mMimeType)) {
665 // Matched at least with one data type
666 matchedTargets.add(target);
667 break;
668 }
669 }
670 }
671
672 if (matchedTargets.isEmpty()) {
673 return new ArrayList<>();
674 }
675
676 // Get the list of all dynamic shortcuts in this package
677 final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
678 findAll(shortcuts, ShortcutInfo::isDynamicVisible, ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
679
680 final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
681 for (int i = 0; i < shortcuts.size(); i++) {
682 final ShortcutInfo si = shortcuts.get(i);
683 for (int j = 0; j < matchedTargets.size(); j++) {
684 // Shortcut must have all of share target categories
685 boolean hasAllCategories = true;
686 final ShareTargetInfo target = matchedTargets.get(j);
687 for (int q = 0; q < target.mCategories.length; q++) {
688 if (!si.getCategories().contains(target.mCategories[q])) {
689 hasAllCategories = false;
690 break;
691 }
692 }
693 if (hasAllCategories) {
694 result.add(new ShortcutManager.ShareShortcutInfo(si, new ComponentName(
695 getPackageName(), target.mTargetClass)));
696 break;
697 }
698 }
699 }
700 return result;
701 }
702
Mehdi Alizadeh85fd3d52019-01-23 12:49:53 -0800703 public boolean hasShareTargets() {
704 return !mShareTargets.isEmpty();
705 }
706
Mehdi Alizadeh406e8b32018-12-11 18:21:49 -0800707 /**
Makoto Onuki6c1dbd52016-05-02 15:19:32 -0700708 * Return the filenames (excluding path names) of icon bitmap files from this package.
709 */
710 public ArraySet<String> getUsedBitmapFiles() {
711 final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
712
713 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
714 final ShortcutInfo si = mShortcuts.valueAt(i);
715 if (si.getBitmapPath() != null) {
716 usedFiles.add(getFileName(si.getBitmapPath()));
717 }
718 }
719 return usedFiles;
720 }
721
722 private static String getFileName(@NonNull String path) {
723 final int sep = path.lastIndexOf(File.separatorChar);
724 if (sep == -1) {
725 return path;
726 } else {
727 return path.substring(sep + 1);
728 }
729 }
730
731 /**
Makoto Onuki4e6cef42016-07-13 16:14:01 -0700732 * @return false if any of the target activities are no longer enabled.
733 */
734 private boolean areAllActivitiesStillEnabled() {
735 if (mShortcuts.size() == 0) {
736 return true;
737 }
738 final ShortcutService s = mShortcutUser.mService;
739
740 // Normally the number of target activities is 1 or so, so no need to use a complex
741 // structure like a set.
742 final ArrayList<ComponentName> checked = new ArrayList<>(4);
743
744 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
745 final ShortcutInfo si = mShortcuts.valueAt(i);
746 final ComponentName activity = si.getActivity();
747
748 if (checked.contains(activity)) {
749 continue; // Already checked.
750 }
751 checked.add(activity);
752
Makoto Onuki40dc2112017-10-18 12:52:45 -0700753 if ((activity != null)
754 && !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
Makoto Onuki4e6cef42016-07-13 16:14:01 -0700755 return false;
756 }
757 }
758 return true;
759 }
760
761 /**
762 * Called when the package may be added or updated, or its activities may be disabled, and
763 * if so, rescan the package and do the necessary stuff.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700764 *
765 * Add case:
766 * - Publish manifest shortcuts.
767 *
768 * Update case:
769 * - Re-publish manifest shortcuts.
770 * - If there are shortcuts with resources (icons or strings), update their timestamps.
Makoto Onuki4e6cef42016-07-13 16:14:01 -0700771 * - Disable shortcuts whose target activities are disabled.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700772 *
773 * @return TRUE if any shortcuts have been changed.
Makoto Onuki39686e82016-04-13 18:03:00 -0700774 */
Makoto Onuki4e6cef42016-07-13 16:14:01 -0700775 public boolean rescanPackageIfNeeded(boolean isNewApp, boolean forceRescan) {
776 final ShortcutService s = mShortcutUser.mService;
Makoto Onuki84d59342018-02-02 09:22:38 -0800777 final long start = s.getStatStartTime();
Makoto Onuki39686e82016-04-13 18:03:00 -0700778
Makoto Onuki4e6cef42016-07-13 16:14:01 -0700779 final PackageInfo pi;
780 try {
781 pi = mShortcutUser.mService.getPackageInfo(
782 getPackageName(), getPackageUserId());
783 if (pi == null) {
784 return false; // Shouldn't happen.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700785 }
Makoto Onuki4e6cef42016-07-13 16:14:01 -0700786
Makoto Onuki248a0ef2016-11-03 15:59:01 -0700787 if (!isNewApp && !forceRescan) {
Makoto Onuki4e6cef42016-07-13 16:14:01 -0700788 // Return if the package hasn't changed, ie:
789 // - version code hasn't change
790 // - lastUpdateTime hasn't change
791 // - all target activities are still enabled.
Makoto Onuki33663282016-08-22 16:19:04 -0700792
793 // Note, system apps timestamps do *not* change after OTAs. (But they do
794 // after an adb sync or a local flash.)
795 // This means if a system app's version code doesn't change on an OTA,
796 // we don't notice it's updated. But that's fine since their version code *should*
797 // really change on OTAs.
Dianne Hackborn3accca02013-09-20 09:32:11 -0700798 if ((getPackageInfo().getVersionCode() == pi.getLongVersionCode())
Makoto Onuki64183d52016-08-08 14:11:34 -0700799 && (getPackageInfo().getLastUpdateTime() == pi.lastUpdateTime)
Makoto Onuki4e6cef42016-07-13 16:14:01 -0700800 && areAllActivitiesStillEnabled()) {
801 return false;
802 }
803 }
804 } finally {
805 s.logDurationStat(Stats.PACKAGE_UPDATE_CHECK, start);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700806 }
807
808 // Now prepare to publish manifest shortcuts.
809 List<ShortcutInfo> newManifestShortcutList = null;
810 try {
811 newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800812 getPackageName(), getPackageUserId(), mShareTargets);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700813 } catch (IOException|XmlPullParserException e) {
814 Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
815 }
816 final int manifestShortcutSize = newManifestShortcutList == null ? 0
817 : newManifestShortcutList.size();
818 if (ShortcutService.DEBUG) {
Mehdi Alizadeh32774622018-11-05 17:32:01 -0800819 Slog.d(TAG,
820 String.format("Package %s has %d manifest shortcut(s), and %d share target(s)",
821 getPackageName(), manifestShortcutSize, mShareTargets.size()));
Makoto Onuki22fcc682016-05-17 14:52:19 -0700822 }
823 if (isNewApp && (manifestShortcutSize == 0)) {
824 // If it's a new app, and it doesn't have manifest shortcuts, then nothing to do.
825
826 // If it's an update, then it may already have manifest shortcuts, which need to be
827 // disabled.
828 return false;
829 }
830 if (ShortcutService.DEBUG) {
831 Slog.d(TAG, String.format("Package %s %s, version %d -> %d", getPackageName(),
832 (isNewApp ? "added" : "updated"),
Dianne Hackborn3accca02013-09-20 09:32:11 -0700833 getPackageInfo().getVersionCode(), pi.getLongVersionCode()));
Makoto Onuki22fcc682016-05-17 14:52:19 -0700834 }
835
Makoto Onukia4f89b12017-10-05 10:37:55 -0700836 getPackageInfo().updateFromPackageInfo(pi);
Dianne Hackborn3accca02013-09-20 09:32:11 -0700837 final long newVersionCode = getPackageInfo().getVersionCode();
Makoto Onukia4f89b12017-10-05 10:37:55 -0700838
839 // See if there are any shortcuts that were prevented restoring because the app was of a
840 // lower version, and re-enable them.
841 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
842 final ShortcutInfo si = mShortcuts.valueAt(i);
843 if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
844 continue;
845 }
846 if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
847 if (ShortcutService.DEBUG) {
848 Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
849 si.getId(), getPackageInfo().getBackupSourceVersionCode()));
850 }
851 continue;
852 }
853 Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
854 si.clearFlags(ShortcutInfo.FLAG_DISABLED);
855 si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
856 }
Makoto Onuki39686e82016-04-13 18:03:00 -0700857
Makoto Onuki22fcc682016-05-17 14:52:19 -0700858 // For existing shortcuts, update timestamps if they have any resources.
Makoto Onukib08790c2016-06-23 14:05:46 -0700859 // Also check if shortcuts' activities are still main activities. Otherwise, disable them.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700860 if (!isNewApp) {
Makoto Onuki157b1622016-06-02 16:13:10 -0700861 Resources publisherRes = null;
862
Makoto Onuki22fcc682016-05-17 14:52:19 -0700863 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
864 final ShortcutInfo si = mShortcuts.valueAt(i);
865
Makoto Onuki2d895c32016-12-02 15:48:40 -0800866 // Disable dynamic shortcuts whose target activity is gone.
Makoto Onukib08790c2016-06-23 14:05:46 -0700867 if (si.isDynamic()) {
Makoto Onuki34145532017-03-14 17:58:36 -0700868 if (si.getActivity() == null) {
869 // Note if it's dynamic, it must have a target activity, but b/36228253.
870 s.wtf("null activity detected.");
871 // TODO Maybe remove it?
872 } else if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) {
Makoto Onukib08790c2016-06-23 14:05:46 -0700873 Slog.w(TAG, String.format(
874 "%s is no longer main activity. Disabling shorcut %s.",
875 getPackageName(), si.getId()));
Makoto Onukia4f89b12017-10-05 10:37:55 -0700876 if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
877 ShortcutInfo.DISABLED_REASON_APP_CHANGED)) {
Makoto Onukib08790c2016-06-23 14:05:46 -0700878 continue; // Actually removed.
879 }
880 // Still pinned, so fall-through and possibly update the resources.
881 }
Makoto Onukib08790c2016-06-23 14:05:46 -0700882 }
883
Makoto Onuki22fcc682016-05-17 14:52:19 -0700884 if (si.hasAnyResources()) {
Makoto Onuki157b1622016-06-02 16:13:10 -0700885 if (!si.isOriginallyFromManifest()) {
886 if (publisherRes == null) {
887 publisherRes = getPackageResources();
888 if (publisherRes == null) {
889 break; // Resources couldn't be loaded.
890 }
891 }
892
893 // If this shortcut is not from a manifest, then update all resource IDs
894 // from resource names. (We don't allow resource strings for
895 // non-manifest at the moment, but icons can still be resources.)
896 si.lookupAndFillInResourceIds(publisherRes);
897 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700898 si.setTimestamp(s.injectCurrentTimeMillis());
899 }
Makoto Onuki39686e82016-04-13 18:03:00 -0700900 }
901 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700902
903 // (Re-)publish manifest shortcut.
Makoto Onuki82fb2eb2017-03-31 16:58:26 -0700904 publishManifestShortcuts(newManifestShortcutList);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700905
Makoto Onuki7001a612016-05-27 13:24:28 -0700906 if (newManifestShortcutList != null) {
Makoto Onuki82fb2eb2017-03-31 16:58:26 -0700907 pushOutExcessShortcuts();
Makoto Onuki7001a612016-05-27 13:24:28 -0700908 }
909
Makoto Onukidf6da042016-06-16 09:51:40 -0700910 s.verifyStates();
911
Makoto Onuki82fb2eb2017-03-31 16:58:26 -0700912 // This will send a notification to the launcher, and also save .
913 s.packageShortcutsChanged(getPackageName(), getPackageUserId());
914 return true; // true means changed.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700915 }
916
917 private boolean publishManifestShortcuts(List<ShortcutInfo> newManifestShortcutList) {
918 if (ShortcutService.DEBUG) {
919 Slog.d(TAG, String.format(
920 "Package %s: publishing manifest shortcuts", getPackageName()));
921 }
922 boolean changed = false;
923
Makoto Onuki22fcc682016-05-17 14:52:19 -0700924 // Keep the previous IDs.
925 ArraySet<String> toDisableList = null;
926 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
927 final ShortcutInfo si = mShortcuts.valueAt(i);
928
929 if (si.isManifestShortcut()) {
930 if (toDisableList == null) {
931 toDisableList = new ArraySet<>();
932 }
933 toDisableList.add(si.getId());
934 }
935 }
936
937 // Publish new ones.
938 if (newManifestShortcutList != null) {
939 final int newListSize = newManifestShortcutList.size();
940
941 for (int i = 0; i < newListSize; i++) {
942 changed = true;
943
944 final ShortcutInfo newShortcut = newManifestShortcutList.get(i);
945 final boolean newDisabled = !newShortcut.isEnabled();
946
Makoto Onuki7001a612016-05-27 13:24:28 -0700947 final String id = newShortcut.getId();
Makoto Onuki22fcc682016-05-17 14:52:19 -0700948 final ShortcutInfo oldShortcut = mShortcuts.get(id);
949
950 boolean wasPinned = false;
951
952 if (oldShortcut != null) {
953 if (!oldShortcut.isOriginallyFromManifest()) {
954 Slog.e(TAG, "Shortcut with ID=" + newShortcut.getId()
955 + " exists but is not from AndroidManifest.xml, not updating.");
956 continue;
957 }
958 // Take over the pinned flag.
959 if (oldShortcut.isPinned()) {
960 wasPinned = true;
961 newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
962 }
963 }
964 if (newDisabled && !wasPinned) {
965 // If the shortcut is disabled, and it was *not* pinned, then this
966 // just doesn't have to be published.
967 // Just keep it in toDisableList, so the previous one would be removed.
968 continue;
969 }
Makoto Onuki22fcc682016-05-17 14:52:19 -0700970
971 // Note even if enabled=false, we still need to update all fields, so do it
972 // regardless.
Makoto Onukia4f89b12017-10-05 10:37:55 -0700973 forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
Makoto Onuki22fcc682016-05-17 14:52:19 -0700974
975 if (!newDisabled && toDisableList != null) {
976 // Still alive, don't remove.
977 toDisableList.remove(id);
978 }
979 }
980 }
981
982 // Disable the previous manifest shortcuts that are no longer in the manifest.
983 if (toDisableList != null) {
984 if (ShortcutService.DEBUG) {
985 Slog.d(TAG, String.format(
986 "Package %s: disabling %d stale shortcuts", getPackageName(),
987 toDisableList.size()));
988 }
989 for (int i = toDisableList.size() - 1; i >= 0; i--) {
990 changed = true;
991
992 final String id = toDisableList.valueAt(i);
993
994 disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
Makoto Onukia4f89b12017-10-05 10:37:55 -0700995 /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
996 ShortcutInfo.DISABLED_REASON_APP_CHANGED);
Makoto Onuki22fcc682016-05-17 14:52:19 -0700997 }
998 removeOrphans();
999 }
Makoto Onukidf6da042016-06-16 09:51:40 -07001000 adjustRanks();
Makoto Onuki22fcc682016-05-17 14:52:19 -07001001 return changed;
Makoto Onuki39686e82016-04-13 18:03:00 -07001002 }
1003
Makoto Onuki7001a612016-05-27 13:24:28 -07001004 /**
1005 * For each target activity, make sure # of dynamic + manifest shortcuts <= max.
1006 * If too many, we'll remove the dynamic with the lowest ranks.
1007 */
1008 private boolean pushOutExcessShortcuts() {
1009 final ShortcutService service = mShortcutUser.mService;
1010 final int maxShortcuts = service.getMaxActivityShortcuts();
1011
1012 boolean changed = false;
1013
1014 final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
1015 sortShortcutsToActivities();
1016 for (int outer = all.size() - 1; outer >= 0; outer--) {
1017 final ArrayList<ShortcutInfo> list = all.valueAt(outer);
1018 if (list.size() <= maxShortcuts) {
1019 continue;
1020 }
1021 // Sort by isManifestShortcut() and getRank().
1022 Collections.sort(list, mShortcutTypeAndRankComparator);
1023
1024 // Keep [0 .. max), and remove (as dynamic) [max .. size)
1025 for (int inner = list.size() - 1; inner >= maxShortcuts; inner--) {
1026 final ShortcutInfo shortcut = list.get(inner);
1027
1028 if (shortcut.isManifestShortcut()) {
1029 // This shouldn't happen -- excess shortcuts should all be non-manifest.
1030 // But just in case.
1031 service.wtf("Found manifest shortcuts in excess list.");
1032 continue;
1033 }
Makoto Onukia4f89b12017-10-05 10:37:55 -07001034 deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true);
Makoto Onuki7001a612016-05-27 13:24:28 -07001035 }
1036 }
Makoto Onuki7001a612016-05-27 13:24:28 -07001037
1038 return changed;
1039 }
1040
1041 /**
1042 * To sort by isManifestShortcut() and getRank(). i.e. manifest shortcuts come before
1043 * non-manifest shortcuts, then sort by rank.
1044 *
1045 * This is used to decide which dynamic shortcuts to remove when an upgraded version has more
1046 * manifest shortcuts than before and as a result we need to remove some of the dynamic
1047 * shortcuts. We sort manifest + dynamic shortcuts by this order, and remove the ones with
1048 * the last ones.
1049 *
1050 * (Note the number of manifest shortcuts is always <= the max number, because if there are
1051 * more, ShortcutParser would ignore the rest.)
1052 */
1053 final Comparator<ShortcutInfo> mShortcutTypeAndRankComparator = (ShortcutInfo a,
1054 ShortcutInfo b) -> {
1055 if (a.isManifestShortcut() && !b.isManifestShortcut()) {
1056 return -1;
1057 }
1058 if (!a.isManifestShortcut() && b.isManifestShortcut()) {
1059 return 1;
1060 }
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001061 return Integer.compare(a.getRank(), b.getRank());
Makoto Onuki7001a612016-05-27 13:24:28 -07001062 };
1063
1064 /**
1065 * Build a list of shortcuts for each target activity and return as a map. The result won't
1066 * contain "floating" shortcuts because they don't belong on any activities.
1067 */
1068 private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
Makoto Onuki7001a612016-05-27 13:24:28 -07001069 final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
1070 = new ArrayMap<>();
1071 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
1072 final ShortcutInfo si = mShortcuts.valueAt(i);
1073 if (si.isFloating()) {
1074 continue; // Ignore floating shortcuts, which are not tied to any activities.
1075 }
1076
1077 final ComponentName activity = si.getActivity();
Makoto Onuki34145532017-03-14 17:58:36 -07001078 if (activity == null) {
1079 mShortcutUser.mService.wtf("null activity detected.");
1080 continue;
1081 }
Makoto Onuki7001a612016-05-27 13:24:28 -07001082
1083 ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
1084 if (list == null) {
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001085 list = new ArrayList<>();
Makoto Onuki7001a612016-05-27 13:24:28 -07001086 activitiesToShortcuts.put(activity, list);
1087 }
1088 list.add(si);
1089 }
1090 return activitiesToShortcuts;
1091 }
1092
1093 /** Used by {@link #enforceShortcutCountsBeforeOperation} */
1094 private void incrementCountForActivity(ArrayMap<ComponentName, Integer> counts,
1095 ComponentName cn, int increment) {
1096 Integer oldValue = counts.get(cn);
1097 if (oldValue == null) {
1098 oldValue = 0;
1099 }
1100
1101 counts.put(cn, oldValue + increment);
1102 }
1103
1104 /**
1105 * Called by
1106 * {@link android.content.pm.ShortcutManager#setDynamicShortcuts},
1107 * {@link android.content.pm.ShortcutManager#addDynamicShortcuts}, and
1108 * {@link android.content.pm.ShortcutManager#updateShortcuts} before actually performing
1109 * the operation to make sure the operation wouldn't result in the target activities having
1110 * more than the allowed number of dynamic/manifest shortcuts.
1111 *
1112 * @param newList shortcut list passed to set, add or updateShortcuts().
1113 * @param operation add, set or update.
1114 * @throws IllegalArgumentException if the operation would result in going over the max
1115 * shortcut count for any activity.
1116 */
1117 public void enforceShortcutCountsBeforeOperation(List<ShortcutInfo> newList,
1118 @ShortcutOperation int operation) {
1119 final ShortcutService service = mShortcutUser.mService;
1120
1121 // Current # of dynamic / manifest shortcuts for each activity.
1122 // (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
1123 // anyway.)
1124 final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
1125 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
1126 final ShortcutInfo shortcut = mShortcuts.valueAt(i);
1127
1128 if (shortcut.isManifestShortcut()) {
1129 incrementCountForActivity(counts, shortcut.getActivity(), 1);
1130 } else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
1131 incrementCountForActivity(counts, shortcut.getActivity(), 1);
1132 }
1133 }
1134
1135 for (int i = newList.size() - 1; i >= 0; i--) {
1136 final ShortcutInfo newShortcut = newList.get(i);
1137 final ComponentName newActivity = newShortcut.getActivity();
1138 if (newActivity == null) {
1139 if (operation != ShortcutService.OPERATION_UPDATE) {
Makoto Onukib08790c2016-06-23 14:05:46 -07001140 service.wtf("Activity must not be null at this point");
1141 continue; // Just ignore this invalid case.
Makoto Onuki7001a612016-05-27 13:24:28 -07001142 }
1143 continue; // Activity can be null for update.
1144 }
1145
1146 final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
1147 if (original == null) {
1148 if (operation == ShortcutService.OPERATION_UPDATE) {
1149 continue; // When updating, ignore if there's no target.
1150 }
1151 // Add() or set(), and there's no existing shortcut with the same ID. We're
1152 // simply publishing (as opposed to updating) this shortcut, so just +1.
1153 incrementCountForActivity(counts, newActivity, 1);
1154 continue;
1155 }
1156 if (original.isFloating() && (operation == ShortcutService.OPERATION_UPDATE)) {
1157 // Updating floating shortcuts doesn't affect the count, so ignore.
1158 continue;
1159 }
1160
1161 // If it's add() or update(), then need to decrement for the previous activity.
1162 // Skip it for set() since it's already been taken care of by not counting the original
1163 // dynamic shortcuts in the first loop.
1164 if (operation != ShortcutService.OPERATION_SET) {
1165 final ComponentName oldActivity = original.getActivity();
1166 if (!original.isFloating()) {
1167 incrementCountForActivity(counts, oldActivity, -1);
1168 }
1169 }
1170 incrementCountForActivity(counts, newActivity, 1);
1171 }
1172
1173 // Then make sure none of the activities have more than the max number of shortcuts.
1174 for (int i = counts.size() - 1; i >= 0; i--) {
1175 service.enforceMaxActivityShortcuts(counts.valueAt(i));
1176 }
1177 }
1178
Makoto Onuki157b1622016-06-02 16:13:10 -07001179 /**
1180 * For all the text fields, refresh the string values if they're from resources.
1181 */
1182 public void resolveResourceStrings() {
1183 final ShortcutService s = mShortcutUser.mService;
1184 boolean changed = false;
1185
1186 Resources publisherRes = null;
1187 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
1188 final ShortcutInfo si = mShortcuts.valueAt(i);
1189
1190 if (si.hasStringResources()) {
1191 changed = true;
1192
1193 if (publisherRes == null) {
1194 publisherRes = getPackageResources();
1195 if (publisherRes == null) {
1196 break; // Resources couldn't be loaded.
1197 }
1198 }
1199
1200 si.resolveResourceStrings(publisherRes);
1201 si.setTimestamp(s.injectCurrentTimeMillis());
1202 }
1203 }
1204 if (changed) {
Makoto Onuki4e6cef42016-07-13 16:14:01 -07001205 s.packageShortcutsChanged(getPackageName(), getPackageUserId());
Makoto Onuki157b1622016-06-02 16:13:10 -07001206 }
1207 }
1208
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001209 /** Clears the implicit ranks for all shortcuts. */
1210 public void clearAllImplicitRanks() {
1211 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
1212 final ShortcutInfo si = mShortcuts.valueAt(i);
1213 si.clearImplicitRankAndRankChangedFlag();
1214 }
1215 }
1216
1217 /**
1218 * Used to sort shortcuts for rank auto-adjusting.
1219 */
1220 final Comparator<ShortcutInfo> mShortcutRankComparator = (ShortcutInfo a, ShortcutInfo b) -> {
1221 // First, sort by rank.
1222 int ret = Integer.compare(a.getRank(), b.getRank());
1223 if (ret != 0) {
1224 return ret;
1225 }
1226 // When ranks are tie, then prioritize the ones that have just been assigned new ranks.
1227 // e.g. when there are 3 shortcuts, "s1" "s2" and "s3" with rank 0, 1, 2 respectively,
1228 // adding a shortcut "s4" with rank 1 will "insert" it between "s1" and "s2", because
1229 // "s2" and "s4" have the same rank 1 but s4 has isRankChanged() set.
1230 // Similarly, updating s3's rank to 1 will insert it between s1 and s2.
1231 if (a.isRankChanged() != b.isRankChanged()) {
1232 return a.isRankChanged() ? -1 : 1;
1233 }
1234 // If they're still tie, sort by implicit rank -- i.e. preserve the order in which
1235 // they're passed to the API.
1236 ret = Integer.compare(a.getImplicitRank(), b.getImplicitRank());
1237 if (ret != 0) {
1238 return ret;
1239 }
Makoto Onukia4f89b12017-10-05 10:37:55 -07001240 // If they're still tie, just sort by their IDs.
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001241 // This may happen with updateShortcuts() -- see
1242 // the testUpdateShortcuts_noManifestShortcuts() test.
1243 return a.getId().compareTo(b.getId());
1244 };
1245
1246 /**
1247 * Re-calculate the ranks for all shortcuts.
1248 */
1249 public void adjustRanks() {
1250 final ShortcutService s = mShortcutUser.mService;
1251 final long now = s.injectCurrentTimeMillis();
1252
1253 // First, clear ranks for floating shortcuts.
1254 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
1255 final ShortcutInfo si = mShortcuts.valueAt(i);
1256 if (si.isFloating()) {
1257 if (si.getRank() != 0) {
1258 si.setTimestamp(now);
1259 si.setRank(0);
1260 }
1261 }
1262 }
1263
1264 // Then adjust ranks. Ranks are unique for each activity, so we first need to sort
1265 // shortcuts to each activity.
1266 // Then sort the shortcuts within each activity with mShortcutRankComparator, and
1267 // assign ranks from 0.
1268 final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
1269 sortShortcutsToActivities();
1270 for (int outer = all.size() - 1; outer >= 0; outer--) { // For each activity.
1271 final ArrayList<ShortcutInfo> list = all.valueAt(outer);
1272
1273 // Sort by ranks and other signals.
1274 Collections.sort(list, mShortcutRankComparator);
1275
1276 int rank = 0;
1277
1278 final int size = list.size();
1279 for (int i = 0; i < size; i++) {
1280 final ShortcutInfo si = list.get(i);
1281 if (si.isManifestShortcut()) {
1282 // Don't adjust ranks for manifest shortcuts.
1283 continue;
1284 }
Makoto Onuki99302b52017-03-29 12:42:26 -07001285 // At this point, it must be dynamic.
1286 if (!si.isDynamic()) {
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001287 s.wtf("Non-dynamic shortcut found.");
1288 continue;
1289 }
1290 final int thisRank = rank++;
1291 if (si.getRank() != thisRank) {
1292 si.setTimestamp(now);
1293 si.setRank(thisRank);
1294 }
1295 }
1296 }
1297 }
1298
Makoto Onukifc4cf2d2016-08-24 11:10:26 -07001299 /** @return true if there's any shortcuts that are not manifest shortcuts. */
1300 public boolean hasNonManifestShortcuts() {
1301 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
1302 final ShortcutInfo si = mShortcuts.valueAt(i);
1303 if (!si.isDeclaredInManifest()) {
1304 return true;
1305 }
1306 }
1307 return false;
1308 }
1309
Makoto Onuki20b82212017-10-04 15:03:50 -07001310 public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
Makoto Onuki31459242016-03-22 11:12:18 -07001311 pw.println();
1312
1313 pw.print(prefix);
1314 pw.print("Package: ");
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001315 pw.print(getPackageName());
Makoto Onuki4d36b3a2016-04-27 12:00:17 -07001316 pw.print(" UID: ");
1317 pw.print(mPackageUid);
Makoto Onuki31459242016-03-22 11:12:18 -07001318 pw.println();
1319
1320 pw.print(prefix);
1321 pw.print(" ");
1322 pw.print("Calls: ");
Makoto Onuki7d0fa812018-02-21 11:24:43 -08001323 pw.print(getApiCallCount(/*unlimited=*/ false));
Makoto Onuki31459242016-03-22 11:12:18 -07001324 pw.println();
1325
Makoto Onuki4d36b3a2016-04-27 12:00:17 -07001326 // getApiCallCount() may have updated mLastKnownForegroundElapsedTime.
1327 pw.print(prefix);
1328 pw.print(" ");
1329 pw.print("Last known FG: ");
1330 pw.print(mLastKnownForegroundElapsedTime);
1331 pw.println();
1332
Makoto Onuki31459242016-03-22 11:12:18 -07001333 // This should be after getApiCallCount(), which may update it.
1334 pw.print(prefix);
1335 pw.print(" ");
1336 pw.print("Last reset: [");
1337 pw.print(mLastResetTime);
1338 pw.print("] ");
Makoto Onukic51b2872016-05-04 15:24:50 -07001339 pw.print(ShortcutService.formatTime(mLastResetTime));
Makoto Onuki31459242016-03-22 11:12:18 -07001340 pw.println();
1341
Makoto Onukic51b2872016-05-04 15:24:50 -07001342 getPackageInfo().dump(pw, prefix + " ");
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001343 pw.println();
1344
Makoto Onuki39686e82016-04-13 18:03:00 -07001345 pw.print(prefix);
1346 pw.println(" Shortcuts:");
Makoto Onuki31459242016-03-22 11:12:18 -07001347 long totalBitmapSize = 0;
1348 final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
1349 final int size = shortcuts.size();
1350 for (int i = 0; i < size; i++) {
1351 final ShortcutInfo si = shortcuts.valueAt(i);
Makoto Onuki6208c672017-10-02 16:20:35 -07001352 pw.println(si.toDumpString(prefix + " "));
Makoto Onuki31459242016-03-22 11:12:18 -07001353 if (si.getBitmapPath() != null) {
1354 final long len = new File(si.getBitmapPath()).length();
Makoto Onuki39686e82016-04-13 18:03:00 -07001355 pw.print(prefix);
1356 pw.print(" ");
Makoto Onuki31459242016-03-22 11:12:18 -07001357 pw.print("bitmap size=");
1358 pw.println(len);
1359
1360 totalBitmapSize += len;
1361 }
1362 }
1363 pw.print(prefix);
1364 pw.print(" ");
1365 pw.print("Total bitmap size: ");
1366 pw.print(totalBitmapSize);
1367 pw.print(" (");
Makoto Onukic51b2872016-05-04 15:24:50 -07001368 pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
Makoto Onuki31459242016-03-22 11:12:18 -07001369 pw.println(")");
1370 }
1371
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001372 @Override
Makoto Onuki76269922016-07-15 14:58:54 -07001373 public JSONObject dumpCheckin(boolean clear) throws JSONException {
1374 final JSONObject result = super.dumpCheckin(clear);
1375
1376 int numDynamic = 0;
1377 int numPinned = 0;
1378 int numManifest = 0;
1379 int numBitmaps = 0;
1380 long totalBitmapSize = 0;
1381
1382 final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
1383 final int size = shortcuts.size();
1384 for (int i = 0; i < size; i++) {
1385 final ShortcutInfo si = shortcuts.valueAt(i);
1386
1387 if (si.isDynamic()) numDynamic++;
1388 if (si.isDeclaredInManifest()) numManifest++;
1389 if (si.isPinned()) numPinned++;
1390
1391 if (si.getBitmapPath() != null) {
1392 numBitmaps++;
1393 totalBitmapSize += new File(si.getBitmapPath()).length();
1394 }
1395 }
1396
1397 result.put(KEY_DYNAMIC, numDynamic);
1398 result.put(KEY_MANIFEST, numManifest);
1399 result.put(KEY_PINNED, numPinned);
1400 result.put(KEY_BITMAPS, numBitmaps);
1401 result.put(KEY_BITMAP_BYTES, totalBitmapSize);
1402
1403 // TODO Log update frequency too.
1404
1405 return result;
1406 }
1407
1408 @Override
Makoto Onuki0acbb142016-03-22 17:02:57 -07001409 public void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
1410 throws IOException, XmlPullParserException {
Makoto Onuki31459242016-03-22 11:12:18 -07001411 final int size = mShortcuts.size();
1412
1413 if (size == 0 && mApiCallCount == 0) {
1414 return; // nothing to write.
1415 }
1416
1417 out.startTag(null, TAG_ROOT);
1418
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001419 ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
Makoto Onuki31459242016-03-22 11:12:18 -07001420 ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
1421 ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
Makoto Onukie3fffa92018-02-28 16:25:40 -08001422 getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
Makoto Onuki31459242016-03-22 11:12:18 -07001423
1424 for (int j = 0; j < size; j++) {
Makoto Onukia4f89b12017-10-05 10:37:55 -07001425 saveShortcut(out, mShortcuts.valueAt(j), forBackup,
1426 getPackageInfo().isBackupAllowed());
Makoto Onuki31459242016-03-22 11:12:18 -07001427 }
1428
1429 out.endTag(null, TAG_ROOT);
1430 }
1431
Makoto Onukia4f89b12017-10-05 10:37:55 -07001432 private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup,
1433 boolean appSupportsBackup)
Makoto Onuki31459242016-03-22 11:12:18 -07001434 throws IOException, XmlPullParserException {
Makoto Onuki475c3652017-05-08 14:29:03 -07001435
1436 final ShortcutService s = mShortcutUser.mService;
1437
Makoto Onuki0acbb142016-03-22 17:02:57 -07001438 if (forBackup) {
Makoto Onukif3ba2e02016-07-12 09:18:50 -07001439 if (!(si.isPinned() && si.isEnabled())) {
Makoto Onukia4f89b12017-10-05 10:37:55 -07001440 // We only backup pinned shortcuts that are enabled.
1441 // Note, this means, shortcuts that are restored but are blocked restore, e.g. due
1442 // to a lower version code, will not be ported to a new device.
1443 return;
Makoto Onuki0acbb142016-03-22 17:02:57 -07001444 }
1445 }
Makoto Onukia4f89b12017-10-05 10:37:55 -07001446 final boolean shouldBackupDetails =
1447 !forBackup // It's not backup
1448 || appSupportsBackup; // Or, it's a backup and app supports backup.
1449
Makoto Onuki475c3652017-05-08 14:29:03 -07001450 // Note: at this point no shortcuts should have bitmaps pending save, but if they do,
1451 // just remove the bitmap.
1452 if (si.isIconPendingSave()) {
1453 s.removeIconLocked(si);
1454 }
Makoto Onuki31459242016-03-22 11:12:18 -07001455 out.startTag(null, TAG_SHORTCUT);
1456 ShortcutService.writeAttr(out, ATTR_ID, si.getId());
1457 // writeAttr(out, "package", si.getPackageName()); // not needed
Makoto Onuki22fcc682016-05-17 14:52:19 -07001458 ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivity());
Makoto Onuki31459242016-03-22 11:12:18 -07001459 // writeAttr(out, "icon", si.getIcon()); // We don't save it.
1460 ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
Makoto Onuki20c95f82016-05-11 16:51:01 -07001461 ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
Makoto Onuki157b1622016-06-02 16:13:10 -07001462 ShortcutService.writeAttr(out, ATTR_TITLE_RES_NAME, si.getTitleResName());
Makoto Onukie3ae7ec2016-03-29 15:45:25 -07001463 ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
Makoto Onuki20c95f82016-05-11 16:51:01 -07001464 ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
Makoto Onuki157b1622016-06-02 16:13:10 -07001465 ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
Makoto Onukia4f89b12017-10-05 10:37:55 -07001466 if (shouldBackupDetails) {
1467 ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
1468 ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
1469 si.getDisabledMessageResourceId());
1470 ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
1471 si.getDisabledMessageResName());
1472 }
1473 ShortcutService.writeAttr(out, ATTR_DISABLED_REASON, si.getDisabledReason());
Makoto Onuki31459242016-03-22 11:12:18 -07001474 ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
1475 si.getLastChangedTimestamp());
Makoto Onuki0acbb142016-03-22 17:02:57 -07001476 if (forBackup) {
1477 // Don't write icon information. Also drop the dynamic flag.
Makoto Onukia4f89b12017-10-05 10:37:55 -07001478
1479 int flags = si.getFlags() &
1480 ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
Makoto Onuki475c3652017-05-08 14:29:03 -07001481 | ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE
Makoto Onukia4f89b12017-10-05 10:37:55 -07001482 | ShortcutInfo.FLAG_DYNAMIC);
1483 ShortcutService.writeAttr(out, ATTR_FLAGS, flags);
1484
1485 // Set the publisher version code at every backup.
Dianne Hackborn3accca02013-09-20 09:32:11 -07001486 final long packageVersionCode = getPackageInfo().getVersionCode();
Makoto Onukia4f89b12017-10-05 10:37:55 -07001487 if (packageVersionCode == 0) {
1488 s.wtf("Package version code should be available at this point.");
1489 // However, 0 is a valid version code, so we just go ahead with it...
1490 }
Makoto Onuki0acbb142016-03-22 17:02:57 -07001491 } else {
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001492 // When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
1493 // as dynamic.
1494 ShortcutService.writeAttr(out, ATTR_RANK, si.getRank());
1495
Makoto Onuki0acbb142016-03-22 17:02:57 -07001496 ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
Makoto Onuki157b1622016-06-02 16:13:10 -07001497 ShortcutService.writeAttr(out, ATTR_ICON_RES_ID, si.getIconResourceId());
1498 ShortcutService.writeAttr(out, ATTR_ICON_RES_NAME, si.getIconResName());
Makoto Onuki0acbb142016-03-22 17:02:57 -07001499 ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
1500 }
Makoto Onuki31459242016-03-22 11:12:18 -07001501
Makoto Onukia4f89b12017-10-05 10:37:55 -07001502 if (shouldBackupDetails) {
1503 {
1504 final Set<String> cat = si.getCategories();
1505 if (cat != null && cat.size() > 0) {
1506 out.startTag(null, TAG_CATEGORIES);
1507 XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
1508 NAME_CATEGORIES, out);
1509 out.endTag(null, TAG_CATEGORIES);
1510 }
Makoto Onukib6d35232016-04-04 15:57:17 -07001511 }
Mehdi Alizadehebb4b602019-02-05 15:52:18 -08001512 if (!forBackup) { // Don't backup the persons field.
1513 final Person[] persons = si.getPersons();
1514 if (!ArrayUtils.isEmpty(persons)) {
1515 for (int i = 0; i < persons.length; i++) {
1516 final Person p = persons[i];
1517
1518 out.startTag(null, TAG_PERSON);
1519 ShortcutService.writeAttr(out, ATTR_PERSON_NAME, p.getName());
1520 ShortcutService.writeAttr(out, ATTR_PERSON_URI, p.getUri());
1521 ShortcutService.writeAttr(out, ATTR_PERSON_KEY, p.getKey());
1522 ShortcutService.writeAttr(out, ATTR_PERSON_IS_BOT, p.isBot());
1523 ShortcutService.writeAttr(out, ATTR_PERSON_IS_IMPORTANT, p.isImportant());
1524 out.endTag(null, TAG_PERSON);
1525 }
1526 }
1527 }
Makoto Onukia4f89b12017-10-05 10:37:55 -07001528 final Intent[] intentsNoExtras = si.getIntentsNoExtras();
1529 final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
1530 final int numIntents = intentsNoExtras.length;
1531 for (int i = 0; i < numIntents; i++) {
1532 out.startTag(null, TAG_INTENT);
1533 ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
1534 ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
1535 out.endTag(null, TAG_INTENT);
1536 }
Makoto Onuki99302b52017-03-29 12:42:26 -07001537
Makoto Onukia4f89b12017-10-05 10:37:55 -07001538 ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
1539 }
Hakan Seyalioglu58fc95d2016-12-13 15:23:22 -08001540
Makoto Onuki31459242016-03-22 11:12:18 -07001541 out.endTag(null, TAG_SHORTCUT);
1542 }
1543
Makoto Onuki4d36b3a2016-04-27 12:00:17 -07001544 public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser,
1545 XmlPullParser parser, boolean fromBackup)
Makoto Onuki31459242016-03-22 11:12:18 -07001546 throws IOException, XmlPullParserException {
1547
1548 final String packageName = ShortcutService.parseStringAttribute(parser,
1549 ATTR_NAME);
1550
Makoto Onukic51b2872016-05-04 15:24:50 -07001551 final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
Makoto Onuki4d36b3a2016-04-27 12:00:17 -07001552 shortcutUser.getUserId(), packageName);
Makoto Onuki31459242016-03-22 11:12:18 -07001553
Makoto Onuki31459242016-03-22 11:12:18 -07001554 ret.mApiCallCount =
1555 ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
1556 ret.mLastResetTime =
1557 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
1558
Makoto Onukia4f89b12017-10-05 10:37:55 -07001559
Makoto Onuki31459242016-03-22 11:12:18 -07001560 final int outerDepth = parser.getDepth();
1561 int type;
1562 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1563 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1564 if (type != XmlPullParser.START_TAG) {
1565 continue;
1566 }
1567 final int depth = parser.getDepth();
1568 final String tag = parser.getName();
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001569 if (depth == outerDepth + 1) {
1570 switch (tag) {
1571 case ShortcutPackageInfo.TAG_ROOT:
Makoto Onuki2e210c42016-03-30 08:30:36 -07001572 ret.getPackageInfo().loadFromXml(parser, fromBackup);
Makoto Onukia4f89b12017-10-05 10:37:55 -07001573
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001574 continue;
1575 case TAG_SHORTCUT:
Makoto Onuki4d36b3a2016-04-27 12:00:17 -07001576 final ShortcutInfo si = parseShortcut(parser, packageName,
Makoto Onukia4f89b12017-10-05 10:37:55 -07001577 shortcutUser.getUserId(), fromBackup);
Makoto Onuki31459242016-03-22 11:12:18 -07001578
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001579 // Don't use addShortcut(), we don't need to save the icon.
1580 ret.mShortcuts.put(si.getId(), si);
1581 continue;
1582 }
Makoto Onuki31459242016-03-22 11:12:18 -07001583 }
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001584 ShortcutService.warnForInvalidTag(depth, tag);
1585 }
Makoto Onuki31459242016-03-22 11:12:18 -07001586 return ret;
1587 }
1588
Makoto Onukiabe84422016-04-07 09:41:19 -07001589 private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName,
Makoto Onukia4f89b12017-10-05 10:37:55 -07001590 @UserIdInt int userId, boolean fromBackup)
1591 throws IOException, XmlPullParserException {
Makoto Onuki31459242016-03-22 11:12:18 -07001592 String id;
1593 ComponentName activityComponent;
1594 // Icon icon;
1595 String title;
Makoto Onuki20c95f82016-05-11 16:51:01 -07001596 int titleResId;
Makoto Onuki157b1622016-06-02 16:13:10 -07001597 String titleResName;
Makoto Onukie3ae7ec2016-03-29 15:45:25 -07001598 String text;
Makoto Onuki20c95f82016-05-11 16:51:01 -07001599 int textResId;
Makoto Onuki157b1622016-06-02 16:13:10 -07001600 String textResName;
Makoto Onuki20c95f82016-05-11 16:51:01 -07001601 String disabledMessage;
1602 int disabledMessageResId;
Makoto Onuki157b1622016-06-02 16:13:10 -07001603 String disabledMessageResName;
Makoto Onukia4f89b12017-10-05 10:37:55 -07001604 int disabledReason;
Makoto Onuki440a1ea2016-07-20 14:21:18 -07001605 Intent intentLegacy;
1606 PersistableBundle intentPersistableExtrasLegacy = null;
1607 ArrayList<Intent> intents = new ArrayList<>();
Makoto Onuki20c95f82016-05-11 16:51:01 -07001608 int rank;
Makoto Onuki31459242016-03-22 11:12:18 -07001609 PersistableBundle extras = null;
1610 long lastChangedTimestamp;
1611 int flags;
Makoto Onuki157b1622016-06-02 16:13:10 -07001612 int iconResId;
1613 String iconResName;
Makoto Onuki31459242016-03-22 11:12:18 -07001614 String bitmapPath;
Makoto Onukia4f89b12017-10-05 10:37:55 -07001615 int backupVersionCode;
Makoto Onukibe73a802016-04-15 14:46:35 -07001616 ArraySet<String> categories = null;
Mehdi Alizadehebb4b602019-02-05 15:52:18 -08001617 ArrayList<Person> persons = new ArrayList<>();
Makoto Onuki31459242016-03-22 11:12:18 -07001618
1619 id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
1620 activityComponent = ShortcutService.parseComponentNameAttribute(parser,
1621 ATTR_ACTIVITY);
1622 title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
Makoto Onuki20c95f82016-05-11 16:51:01 -07001623 titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID);
Makoto Onuki157b1622016-06-02 16:13:10 -07001624 titleResName = ShortcutService.parseStringAttribute(parser, ATTR_TITLE_RES_NAME);
Makoto Onukie3ae7ec2016-03-29 15:45:25 -07001625 text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
Makoto Onuki20c95f82016-05-11 16:51:01 -07001626 textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID);
Makoto Onuki157b1622016-06-02 16:13:10 -07001627 textResName = ShortcutService.parseStringAttribute(parser, ATTR_TEXT_RES_NAME);
Makoto Onuki20c95f82016-05-11 16:51:01 -07001628 disabledMessage = ShortcutService.parseStringAttribute(parser, ATTR_DISABLED_MESSAGE);
1629 disabledMessageResId = ShortcutService.parseIntAttribute(parser,
1630 ATTR_DISABLED_MESSAGE_RES_ID);
Makoto Onuki157b1622016-06-02 16:13:10 -07001631 disabledMessageResName = ShortcutService.parseStringAttribute(parser,
1632 ATTR_DISABLED_MESSAGE_RES_NAME);
Makoto Onukia4f89b12017-10-05 10:37:55 -07001633 disabledReason = ShortcutService.parseIntAttribute(parser, ATTR_DISABLED_REASON);
Makoto Onuki440a1ea2016-07-20 14:21:18 -07001634 intentLegacy = ShortcutService.parseIntentAttributeNoDefault(parser, ATTR_INTENT_LEGACY);
Makoto Onuki20c95f82016-05-11 16:51:01 -07001635 rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
Makoto Onuki9da23fc2016-03-29 11:14:42 -07001636 lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
Makoto Onuki31459242016-03-22 11:12:18 -07001637 flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
Makoto Onuki157b1622016-06-02 16:13:10 -07001638 iconResId = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES_ID);
1639 iconResName = ShortcutService.parseStringAttribute(parser, ATTR_ICON_RES_NAME);
Makoto Onuki31459242016-03-22 11:12:18 -07001640 bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
1641
1642 final int outerDepth = parser.getDepth();
1643 int type;
1644 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1645 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1646 if (type != XmlPullParser.START_TAG) {
1647 continue;
1648 }
1649 final int depth = parser.getDepth();
1650 final String tag = parser.getName();
1651 if (ShortcutService.DEBUG_LOAD) {
1652 Slog.d(TAG, String.format(" depth=%d type=%d name=%s",
1653 depth, type, tag));
1654 }
1655 switch (tag) {
Makoto Onuki440a1ea2016-07-20 14:21:18 -07001656 case TAG_INTENT_EXTRAS_LEGACY:
1657 intentPersistableExtrasLegacy = PersistableBundle.restoreFromXml(parser);
1658 continue;
1659 case TAG_INTENT:
1660 intents.add(parseIntent(parser));
Makoto Onuki31459242016-03-22 11:12:18 -07001661 continue;
1662 case TAG_EXTRAS:
1663 extras = PersistableBundle.restoreFromXml(parser);
1664 continue;
Makoto Onukib6d35232016-04-04 15:57:17 -07001665 case TAG_CATEGORIES:
1666 // This just contains string-array.
1667 continue;
Mehdi Alizadehebb4b602019-02-05 15:52:18 -08001668 case TAG_PERSON:
1669 persons.add(parsePerson(parser));
1670 continue;
Makoto Onukib6d35232016-04-04 15:57:17 -07001671 case TAG_STRING_ARRAY_XMLUTILS:
1672 if (NAME_CATEGORIES.equals(ShortcutService.parseStringAttribute(parser,
1673 ATTR_NAME_XMLUTILS))) {
Makoto Onukibe73a802016-04-15 14:46:35 -07001674 final String[] ar = XmlUtils.readThisStringArrayXml(
1675 parser, TAG_STRING_ARRAY_XMLUTILS, null);
1676 categories = new ArraySet<>(ar.length);
1677 for (int i = 0; i < ar.length; i++) {
1678 categories.add(ar[i]);
1679 }
Makoto Onukib6d35232016-04-04 15:57:17 -07001680 }
1681 continue;
Makoto Onuki31459242016-03-22 11:12:18 -07001682 }
1683 throw ShortcutService.throwForInvalidTag(depth, tag);
1684 }
Makoto Onukibe73a802016-04-15 14:46:35 -07001685
Makoto Onuki440a1ea2016-07-20 14:21:18 -07001686 if (intentLegacy != null) {
1687 // For the legacy file format which supported only one intent per shortcut.
1688 ShortcutInfo.setIntentExtras(intentLegacy, intentPersistableExtrasLegacy);
1689 intents.clear();
1690 intents.add(intentLegacy);
1691 }
Makoto Onuki9fd90192017-01-06 18:31:03 +00001692
Makoto Onukia4f89b12017-10-05 10:37:55 -07001693
1694 if ((disabledReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
1695 && ((flags & ShortcutInfo.FLAG_DISABLED) != 0)) {
1696 // We didn't used to have the disabled reason, so if a shortcut is disabled
1697 // and has no reason, we assume it was disabled by publisher.
1698 disabledReason = ShortcutInfo.DISABLED_REASON_BY_APP;
1699 }
1700
1701 // All restored shortcuts are initially "shadow".
1702 if (fromBackup) {
1703 flags |= ShortcutInfo.FLAG_SHADOW;
1704 }
1705
Felipe Leme90205ef2019-03-05 09:59:52 -08001706 LocusId locusId = null; // LocusId is not set on XML.
1707
Makoto Onuki31459242016-03-22 11:12:18 -07001708 return new ShortcutInfo(
Felipe Leme90205ef2019-03-05 09:59:52 -08001709 userId, id, packageName, activityComponent, /* icon= */ null,
Makoto Onuki157b1622016-06-02 16:13:10 -07001710 title, titleResId, titleResName, text, textResId, textResName,
1711 disabledMessage, disabledMessageResId, disabledMessageResName,
Makoto Onuki440a1ea2016-07-20 14:21:18 -07001712 categories,
1713 intents.toArray(new Intent[intents.size()]),
1714 rank, extras, lastChangedTimestamp, flags,
Mehdi Alizadehebb4b602019-02-05 15:52:18 -08001715 iconResId, iconResName, bitmapPath, disabledReason,
Felipe Leme90205ef2019-03-05 09:59:52 -08001716 persons.toArray(new Person[persons.size()]), locusId);
Makoto Onuki31459242016-03-22 11:12:18 -07001717 }
Makoto Onuki2e210c42016-03-30 08:30:36 -07001718
Makoto Onuki440a1ea2016-07-20 14:21:18 -07001719 private static Intent parseIntent(XmlPullParser parser)
1720 throws IOException, XmlPullParserException {
1721
1722 Intent intent = ShortcutService.parseIntentAttribute(parser,
1723 ATTR_INTENT_NO_EXTRA);
1724
1725 final int outerDepth = parser.getDepth();
1726 int type;
1727 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1728 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1729 if (type != XmlPullParser.START_TAG) {
1730 continue;
1731 }
1732 final int depth = parser.getDepth();
1733 final String tag = parser.getName();
1734 if (ShortcutService.DEBUG_LOAD) {
1735 Slog.d(TAG, String.format(" depth=%d type=%d name=%s",
1736 depth, type, tag));
1737 }
1738 switch (tag) {
1739 case TAG_EXTRAS:
1740 ShortcutInfo.setIntentExtras(intent,
1741 PersistableBundle.restoreFromXml(parser));
1742 continue;
1743 }
1744 throw ShortcutService.throwForInvalidTag(depth, tag);
1745 }
1746 return intent;
1747 }
1748
Mehdi Alizadehebb4b602019-02-05 15:52:18 -08001749 private static Person parsePerson(XmlPullParser parser)
1750 throws IOException, XmlPullParserException {
1751 CharSequence name = ShortcutService.parseStringAttribute(parser, ATTR_PERSON_NAME);
1752 String uri = ShortcutService.parseStringAttribute(parser, ATTR_PERSON_URI);
1753 String key = ShortcutService.parseStringAttribute(parser, ATTR_PERSON_KEY);
1754 boolean isBot = ShortcutService.parseBooleanAttribute(parser, ATTR_PERSON_IS_BOT);
1755 boolean isImportant = ShortcutService.parseBooleanAttribute(parser,
1756 ATTR_PERSON_IS_IMPORTANT);
1757
1758 Person.Builder builder = new Person.Builder();
1759 builder.setName(name).setUri(uri).setKey(key).setBot(isBot).setImportant(isImportant);
1760 return builder.build();
1761 }
1762
Makoto Onuki2e210c42016-03-30 08:30:36 -07001763 @VisibleForTesting
1764 List<ShortcutInfo> getAllShortcutsForTest() {
1765 return new ArrayList<>(mShortcuts.values());
1766 }
Makoto Onuki7001a612016-05-27 13:24:28 -07001767
Mehdi Alizadeh32774622018-11-05 17:32:01 -08001768 @VisibleForTesting
1769 List<ShareTargetInfo> getAllShareTargetsForTest() {
1770 return new ArrayList<>(mShareTargets);
1771 }
1772
Makoto Onuki7001a612016-05-27 13:24:28 -07001773 @Override
1774 public void verifyStates() {
1775 super.verifyStates();
1776
1777 boolean failed = false;
1778
Makoto Onuki255461f2017-01-10 11:47:25 -08001779 final ShortcutService s = mShortcutUser.mService;
1780
Makoto Onuki7001a612016-05-27 13:24:28 -07001781 final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
1782 sortShortcutsToActivities();
1783
1784 // Make sure each activity won't have more than max shortcuts.
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001785 for (int outer = all.size() - 1; outer >= 0; outer--) {
1786 final ArrayList<ShortcutInfo> list = all.valueAt(outer);
1787 if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
Makoto Onuki7001a612016-05-27 13:24:28 -07001788 failed = true;
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001789 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
1790 + " has " + all.valueAt(outer).size() + " shortcuts.");
Makoto Onuki7001a612016-05-27 13:24:28 -07001791 }
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001792
1793 // Sort by rank.
1794 Collections.sort(list, (a, b) -> Integer.compare(a.getRank(), b.getRank()));
1795
1796 // Split into two arrays for each kind.
1797 final ArrayList<ShortcutInfo> dynamicList = new ArrayList<>(list);
1798 dynamicList.removeIf((si) -> !si.isDynamic());
1799
1800 final ArrayList<ShortcutInfo> manifestList = new ArrayList<>(list);
1801 dynamicList.removeIf((si) -> !si.isManifestShortcut());
1802
1803 verifyRanksSequential(dynamicList);
1804 verifyRanksSequential(manifestList);
Makoto Onuki7001a612016-05-27 13:24:28 -07001805 }
1806
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001807 // Verify each shortcut's status.
Makoto Onuki7001a612016-05-27 13:24:28 -07001808 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
1809 final ShortcutInfo si = mShortcuts.valueAt(i);
Makoto Onuki99302b52017-03-29 12:42:26 -07001810 if (!(si.isDeclaredInManifest() || si.isDynamic() || si.isPinned())) {
Makoto Onuki7001a612016-05-27 13:24:28 -07001811 failed = true;
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001812 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
Makoto Onuki99302b52017-03-29 12:42:26 -07001813 + " is not manifest, dynamic or pinned.");
Makoto Onuki7001a612016-05-27 13:24:28 -07001814 }
Makoto Onukiff14f732016-06-30 17:07:25 -07001815 if (si.isDeclaredInManifest() && si.isDynamic()) {
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001816 failed = true;
1817 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
1818 + " is both dynamic and manifest at the same time.");
1819 }
Makoto Onuki255461f2017-01-10 11:47:25 -08001820 if (si.getActivity() == null && !si.isFloating()) {
Makoto Onuki7001a612016-05-27 13:24:28 -07001821 failed = true;
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001822 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
Makoto Onuki255461f2017-01-10 11:47:25 -08001823 + " has null activity, but not floating.");
Makoto Onuki7001a612016-05-27 13:24:28 -07001824 }
Makoto Onuki9fd90192017-01-06 18:31:03 +00001825 if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
Makoto Onuki7001a612016-05-27 13:24:28 -07001826 failed = true;
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001827 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
Makoto Onuki7001a612016-05-27 13:24:28 -07001828 + " is not floating, but is disabled.");
1829 }
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001830 if (si.isFloating() && si.getRank() != 0) {
1831 failed = true;
1832 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
1833 + " is floating, but has rank=" + si.getRank());
1834 }
Makoto Onukidd097812016-06-29 13:10:09 -07001835 if (si.getIcon() != null) {
1836 failed = true;
1837 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
1838 + " still has an icon");
1839 }
Hyunyoung Songe4179e22017-03-01 12:51:26 -08001840 if (si.hasAdaptiveBitmap() && !si.hasIconFile()) {
Hyunyoung Songf281e7a2017-02-13 10:57:42 -08001841 failed = true;
1842 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
Hyunyoung Songe4179e22017-03-01 12:51:26 -08001843 + " has adaptive bitmap but was not saved to a file.");
Hyunyoung Songf281e7a2017-02-13 10:57:42 -08001844 }
Makoto Onukidd097812016-06-29 13:10:09 -07001845 if (si.hasIconFile() && si.hasIconResource()) {
1846 failed = true;
1847 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
1848 + " has both resource and bitmap icons");
1849 }
Makoto Onukia4f89b12017-10-05 10:37:55 -07001850 if (si.isEnabled()
1851 != (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
1852 failed = true;
1853 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
1854 + " isEnabled() and getDisabledReason() disagree: "
1855 + si.isEnabled() + " vs " + si.getDisabledReason());
1856 }
1857 if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
1858 && (getPackageInfo().getBackupSourceVersionCode()
1859 == ShortcutInfo.VERSION_CODE_UNKNOWN)) {
1860 failed = true;
1861 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
1862 + " RESTORED_VERSION_LOWER with no backup source version code.");
1863 }
Makoto Onuki255461f2017-01-10 11:47:25 -08001864 if (s.isDummyMainActivity(si.getActivity())) {
1865 failed = true;
1866 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
1867 + " has a dummy target activity");
1868 }
Makoto Onuki7001a612016-05-27 13:24:28 -07001869 }
1870
1871 if (failed) {
Makoto Onuki9fd90192017-01-06 18:31:03 +00001872 throw new IllegalStateException("See logcat for errors");
Makoto Onuki7001a612016-05-27 13:24:28 -07001873 }
1874 }
Makoto Onuki9e1f5592016-06-08 12:30:23 -07001875
1876 private boolean verifyRanksSequential(List<ShortcutInfo> list) {
1877 boolean failed = false;
1878
1879 for (int i = 0; i < list.size(); i++) {
1880 final ShortcutInfo si = list.get(i);
1881 if (si.getRank() != i) {
1882 failed = true;
1883 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
1884 + " rank=" + si.getRank() + " but expected to be "+ i);
1885 }
1886 }
1887 return failed;
1888 }
Makoto Onuki31459242016-03-22 11:12:18 -07001889}