blob: 151f61e3919258a5e35541673981897dacde6f4d [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;
Makoto Onuki31459242016-03-22 11:12:18 -070021import android.content.ComponentName;
22import android.content.Intent;
23import android.content.pm.ShortcutInfo;
24import android.os.PersistableBundle;
25import android.text.format.Formatter;
26import android.util.ArrayMap;
27import android.util.ArraySet;
28import android.util.Slog;
29
Makoto Onuki2e210c42016-03-30 08:30:36 -070030import com.android.internal.annotations.VisibleForTesting;
Makoto Onukib6d35232016-04-04 15:57:17 -070031import com.android.internal.util.XmlUtils;
Makoto Onuki2e210c42016-03-30 08:30:36 -070032
Makoto Onuki31459242016-03-22 11:12:18 -070033import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35import org.xmlpull.v1.XmlSerializer;
36
37import java.io.File;
38import java.io.IOException;
39import java.io.PrintWriter;
40import java.util.ArrayList;
41import java.util.List;
Makoto Onukibe73a802016-04-15 14:46:35 -070042import java.util.Set;
Makoto Onuki31459242016-03-22 11:12:18 -070043import java.util.function.Predicate;
44
45/**
46 * Package information used by {@link ShortcutService}.
47 */
Makoto Onuki9da23fc2016-03-29 11:14:42 -070048class ShortcutPackage extends ShortcutPackageItem {
Makoto Onuki31459242016-03-22 11:12:18 -070049 private static final String TAG = ShortcutService.TAG;
50
51 static final String TAG_ROOT = "package";
52 private static final String TAG_INTENT_EXTRAS = "intent-extras";
53 private static final String TAG_EXTRAS = "extras";
54 private static final String TAG_SHORTCUT = "shortcut";
Makoto Onukib6d35232016-04-04 15:57:17 -070055 private static final String TAG_CATEGORIES = "categories";
Makoto Onuki31459242016-03-22 11:12:18 -070056
57 private static final String ATTR_NAME = "name";
58 private static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
59 private static final String ATTR_CALL_COUNT = "call-count";
60 private static final String ATTR_LAST_RESET = "last-reset";
61 private static final String ATTR_ID = "id";
62 private static final String ATTR_ACTIVITY = "activity";
63 private static final String ATTR_TITLE = "title";
Makoto Onukie3ae7ec2016-03-29 15:45:25 -070064 private static final String ATTR_TEXT = "text";
Makoto Onuki31459242016-03-22 11:12:18 -070065 private static final String ATTR_INTENT = "intent";
66 private static final String ATTR_WEIGHT = "weight";
67 private static final String ATTR_TIMESTAMP = "timestamp";
68 private static final String ATTR_FLAGS = "flags";
69 private static final String ATTR_ICON_RES = "icon-res";
70 private static final String ATTR_BITMAP_PATH = "bitmap-path";
71
Makoto Onukib6d35232016-04-04 15:57:17 -070072 private static final String NAME_CATEGORIES = "categories";
73
74 private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array";
75 private static final String ATTR_NAME_XMLUTILS = "name";
76
Makoto Onuki31459242016-03-22 11:12:18 -070077 /**
78 * All the shortcuts from the package, keyed on IDs.
79 */
80 final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
81
82 /**
83 * # of dynamic shortcuts.
84 */
85 private int mDynamicShortcutCount = 0;
86
87 /**
88 * # of times the package has called rate-limited APIs.
89 */
90 private int mApiCallCount;
91
92 /**
93 * When {@link #mApiCallCount} was reset last time.
94 */
95 private long mLastResetTime;
96
Makoto Onuki4d36b3a2016-04-27 12:00:17 -070097 private final int mPackageUid;
98
99 private long mLastKnownForegroundElapsedTime;
100
101 private ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
102 int packageUserId, String packageName, ShortcutPackageInfo spi) {
103 super(shortcutUser, packageUserId, packageName,
104 spi != null ? spi : ShortcutPackageInfo.newEmpty());
105
106 mPackageUid = s.injectGetPackageUid(packageName, packageUserId);
Makoto Onuki31459242016-03-22 11:12:18 -0700107 }
108
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700109 public ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
110 int packageUserId, String packageName) {
111 this(s, shortcutUser, packageUserId, packageName, null);
Makoto Onuki0acbb142016-03-22 17:02:57 -0700112 }
113
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700114 @Override
115 public int getOwnerUserId() {
116 // For packages, always owner user == package user.
117 return getPackageUserId();
Makoto Onuki0acbb142016-03-22 17:02:57 -0700118 }
119
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700120 public int getPackageUid() {
121 return mPackageUid;
122 }
123
Makoto Onuki39686e82016-04-13 18:03:00 -0700124 /**
125 * Called when a shortcut is about to be published. At this point we know the publisher package
126 * exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
127 * we do some initialization for the package.
128 */
129 private void onShortcutPublish(ShortcutService s) {
130 // Make sure we have the version code for the app. We need the version code in
131 // handlePackageUpdated().
132 if (getPackageInfo().getVersionCode() < 0) {
133 final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId());
134 if (ShortcutService.DEBUG) {
135 Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
136 versionCode));
137 }
138 if (versionCode >= 0) {
139 getPackageInfo().setVersionCode(versionCode);
140 s.scheduleSaveUser(getOwnerUserId());
141 }
142 }
143 }
144
Makoto Onuki2e210c42016-03-30 08:30:36 -0700145 @Override
146 protected void onRestoreBlocked(ShortcutService s) {
147 // Can't restore due to version/signature mismatch. Remove all shortcuts.
148 mShortcuts.clear();
149 }
150
151 @Override
152 protected void onRestored(ShortcutService s) {
153 // Because some launchers may not have been restored (e.g. allowBackup=false),
154 // we need to re-calculate the pinned shortcuts.
155 refreshPinnedFlags(s);
156 }
157
Makoto Onukid99c6f02016-03-28 11:02:54 -0700158 /**
159 * Note this does *not* provide a correct view to the calling launcher.
160 */
Makoto Onuki31459242016-03-22 11:12:18 -0700161 @Nullable
162 public ShortcutInfo findShortcutById(String id) {
163 return mShortcuts.get(id);
164 }
165
166 private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
167 @NonNull String id) {
168 final ShortcutInfo shortcut = mShortcuts.remove(id);
169 if (shortcut != null) {
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700170 s.removeIcon(getPackageUserId(), shortcut);
Makoto Onuki31459242016-03-22 11:12:18 -0700171 shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
172 }
173 return shortcut;
174 }
175
176 void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
177 deleteShortcut(s, newShortcut.getId());
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700178 s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
Makoto Onuki31459242016-03-22 11:12:18 -0700179 mShortcuts.put(newShortcut.getId(), newShortcut);
180 }
181
182 /**
183 * Add a shortcut, or update one with the same ID, with taking over existing flags.
184 *
185 * It checks the max number of dynamic shortcuts.
186 */
187 public void addDynamicShortcut(@NonNull ShortcutService s,
188 @NonNull ShortcutInfo newShortcut) {
Makoto Onuki39686e82016-04-13 18:03:00 -0700189
190 onShortcutPublish(s);
191
Makoto Onuki31459242016-03-22 11:12:18 -0700192 newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
193
194 final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
195
196 final boolean wasPinned;
197 final int newDynamicCount;
198
199 if (oldShortcut == null) {
200 wasPinned = false;
201 newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
202 } else {
203 wasPinned = oldShortcut.isPinned();
204 if (oldShortcut.isDynamic()) {
205 newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut.
206 } else {
207 newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
208 }
209 }
210
211 // Make sure there's still room.
212 s.enforceMaxDynamicShortcuts(newDynamicCount);
213
214 // Okay, make it dynamic and add.
215 if (wasPinned) {
216 newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
217 }
218
219 addShortcut(s, newShortcut);
220 mDynamicShortcutCount = newDynamicCount;
221 }
222
223 /**
224 * Remove all shortcuts that aren't pinned nor dynamic.
225 */
226 private void removeOrphans(@NonNull ShortcutService s) {
227 ArrayList<String> removeList = null; // Lazily initialize.
228
229 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
230 final ShortcutInfo si = mShortcuts.valueAt(i);
231
232 if (si.isPinned() || si.isDynamic()) continue;
233
234 if (removeList == null) {
235 removeList = new ArrayList<>();
236 }
237 removeList.add(si.getId());
238 }
239 if (removeList != null) {
240 for (int i = removeList.size() - 1; i >= 0; i--) {
241 deleteShortcut(s, removeList.get(i));
242 }
243 }
244 }
245
246 /**
247 * Remove all dynamic shortcuts.
248 */
249 public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
250 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
251 mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
252 }
253 removeOrphans(s);
254 mDynamicShortcutCount = 0;
255 }
256
257 /**
258 * Remove a dynamic shortcut by ID.
259 */
260 public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
261 final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
262
263 if (oldShortcut == null) {
264 return;
265 }
266 if (oldShortcut.isDynamic()) {
267 mDynamicShortcutCount--;
268 }
269 if (oldShortcut.isPinned()) {
270 oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
271 } else {
272 deleteShortcut(s, shortcutId);
273 }
274 }
275
276 /**
277 * Called after a launcher updates the pinned set. For each shortcut in this package,
278 * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it.
279 *
280 * <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
281 */
282 public void refreshPinnedFlags(@NonNull ShortcutService s) {
283 // First, un-pin all shortcuts
284 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
285 mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
286 }
287
288 // Then, for the pinned set for each launcher, set the pin flag one by one.
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700289 s.getUserShortcutsLocked(getPackageUserId()).forAllLaunchers(launcherShortcuts -> {
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700290 final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
Makoto Onuki2e210c42016-03-30 08:30:36 -0700291 getPackageName(), getPackageUserId());
Makoto Onuki31459242016-03-22 11:12:18 -0700292
293 if (pinned == null || pinned.size() == 0) {
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700294 return;
Makoto Onuki31459242016-03-22 11:12:18 -0700295 }
296 for (int i = pinned.size() - 1; i >= 0; i--) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700297 final String id = pinned.valueAt(i);
298 final ShortcutInfo si = mShortcuts.get(id);
Makoto Onuki31459242016-03-22 11:12:18 -0700299 if (si == null) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700300 // This happens if a launcher pinned shortcuts from this package, then backup&
301 // restored, but this package doesn't allow backing up.
302 // In that case the launcher ends up having a dangling pinned shortcuts.
303 // That's fine, when the launcher is restored, we'll fix it.
304 continue;
Makoto Onuki31459242016-03-22 11:12:18 -0700305 }
Makoto Onuki2e210c42016-03-30 08:30:36 -0700306 si.addFlags(ShortcutInfo.FLAG_PINNED);
Makoto Onuki31459242016-03-22 11:12:18 -0700307 }
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700308 });
Makoto Onuki31459242016-03-22 11:12:18 -0700309
310 // Lastly, remove the ones that are no longer pinned nor dynamic.
311 removeOrphans(s);
312 }
313
314 /**
315 * Number of calls that the caller has made, since the last reset.
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700316 *
317 * <p>This takes care of the resetting the counter for foreground apps as well as after
318 * locale changes.
Makoto Onuki31459242016-03-22 11:12:18 -0700319 */
320 public int getApiCallCount(@NonNull ShortcutService s) {
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700321 mShortcutUser.resetThrottlingIfNeeded(s);
322
323 // Reset the counter if:
324 // - the package is in foreground now.
325 // - the package is *not* in foreground now, but was in foreground at some point
326 // since the previous time it had been.
327 if (s.isUidForegroundLocked(mPackageUid)
328 || mLastKnownForegroundElapsedTime
329 < s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) {
330 mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
331 resetRateLimiting(s);
332 }
333
334 // Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount,
335 // but we just can't return 0 at this point, because we may have to update
336 // mLastResetTime.
337
Makoto Onuki31459242016-03-22 11:12:18 -0700338 final long last = s.getLastResetTimeLocked();
339
340 final long now = s.injectCurrentTimeMillis();
341 if (ShortcutService.isClockValid(now) && mLastResetTime > now) {
342 Slog.w(TAG, "Clock rewound");
343 // Clock rewound.
344 mLastResetTime = now;
345 mApiCallCount = 0;
346 return mApiCallCount;
347 }
348
349 // If not reset yet, then reset.
350 if (mLastResetTime < last) {
351 if (ShortcutService.DEBUG) {
352 Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting",
353 mLastResetTime, now, last));
354 }
355 mApiCallCount = 0;
356 mLastResetTime = last;
357 }
358 return mApiCallCount;
359 }
360
361 /**
362 * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount}
363 * and return true. Otherwise just return false.
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700364 *
365 * <p>This takes care of the resetting the counter for foreground apps as well as after
366 * locale changes, which is done internally by {@link #getApiCallCount}.
Makoto Onuki31459242016-03-22 11:12:18 -0700367 */
368 public boolean tryApiCall(@NonNull ShortcutService s) {
Makoto Onukib6d35232016-04-04 15:57:17 -0700369 if (getApiCallCount(s) >= s.mMaxUpdatesPerInterval) {
Makoto Onuki31459242016-03-22 11:12:18 -0700370 return false;
371 }
372 mApiCallCount++;
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700373 s.scheduleSaveUser(getOwnerUserId());
Makoto Onuki31459242016-03-22 11:12:18 -0700374 return true;
375 }
376
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700377 public void resetRateLimiting(@NonNull ShortcutService s) {
378 if (ShortcutService.DEBUG) {
379 Slog.d(TAG, "resetRateLimiting: " + getPackageName());
380 }
381 if (mApiCallCount > 0) {
382 mApiCallCount = 0;
383 s.scheduleSaveUser(getOwnerUserId());
384 }
385 }
386
387 public void resetRateLimitingForCommandLineNoSaving() {
Makoto Onuki31459242016-03-22 11:12:18 -0700388 mApiCallCount = 0;
389 mLastResetTime = 0;
390 }
391
392 /**
393 * Find all shortcuts that match {@code query}.
394 */
395 public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
Makoto Onukid99c6f02016-03-28 11:02:54 -0700396 @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
397 findAll(s, result, query, cloneFlag, null, 0);
398 }
399
400 /**
401 * Find all shortcuts that match {@code query}.
402 *
403 * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned
404 * by the calling launcher will not be included in the result, and also "isPinned" will be
405 * adjusted for the caller too.
406 */
407 public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
Makoto Onuki31459242016-03-22 11:12:18 -0700408 @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
Makoto Onukid99c6f02016-03-28 11:02:54 -0700409 @Nullable String callingLauncher, int launcherUserId) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700410 if (getPackageInfo().isShadow()) {
411 // Restored and the app not installed yet, so don't return any.
412 return;
413 }
Makoto Onuki31459242016-03-22 11:12:18 -0700414
415 // Set of pinned shortcuts by the calling launcher.
416 final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
Makoto Onuki2e210c42016-03-30 08:30:36 -0700417 : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
418 .getPinnedShortcutIds(getPackageName(), getPackageUserId());
Makoto Onuki31459242016-03-22 11:12:18 -0700419
420 for (int i = 0; i < mShortcuts.size(); i++) {
421 final ShortcutInfo si = mShortcuts.valueAt(i);
422
423 // If it's called by non-launcher (i.e. publisher, always include -> true.
424 // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned
425 // it.
426 final boolean isPinnedByCaller = (callingLauncher == null)
427 || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
428 if (!si.isDynamic()) {
429 if (!si.isPinned()) {
Makoto Onuki2e210c42016-03-30 08:30:36 -0700430 s.wtf("Shortcut not pinned: package " + getPackageName()
431 + ", user=" + getPackageUserId() + ", id=" + si.getId());
Makoto Onuki31459242016-03-22 11:12:18 -0700432 continue;
433 }
434 if (!isPinnedByCaller) {
435 continue;
436 }
437 }
438 final ShortcutInfo clone = si.clone(cloneFlag);
439 // Fix up isPinned for the caller. Note we need to do it before the "test" callback,
440 // since it may check isPinned.
441 if (!isPinnedByCaller) {
442 clone.clearFlags(ShortcutInfo.FLAG_PINNED);
443 }
444 if (query == null || query.test(clone)) {
445 result.add(clone);
446 }
447 }
448 }
449
450 public void resetThrottling() {
451 mApiCallCount = 0;
452 }
453
Makoto Onuki39686e82016-04-13 18:03:00 -0700454 /**
455 * Called when the package is updated. If there are shortcuts with resource icons, update
456 * their timestamps.
457 */
458 public void handlePackageUpdated(ShortcutService s, int newVersionCode) {
459 if (getPackageInfo().getVersionCode() >= newVersionCode) {
460 // Version hasn't changed; nothing to do.
461 return;
462 }
463 if (ShortcutService.DEBUG) {
464 Slog.d(TAG, String.format("Package %s updated, version %d -> %d", getPackageName(),
465 getPackageInfo().getVersionCode(), newVersionCode));
466 }
467
468 getPackageInfo().setVersionCode(newVersionCode);
469
470 boolean changed = false;
471 for (int i = mShortcuts.size() - 1; i >= 0; i--) {
472 final ShortcutInfo si = mShortcuts.valueAt(i);
473
474 if (si.hasIconResource()) {
475 changed = true;
476 si.setTimestamp(s.injectCurrentTimeMillis());
477 }
478 }
479 if (changed) {
480 // This will send a notification to the launcher, and also save .
481 s.packageShortcutsChanged(getPackageName(), getPackageUserId());
482 } else {
483 // Still save the version code.
484 s.scheduleSaveUser(getPackageUserId());
485 }
486 }
487
Makoto Onuki31459242016-03-22 11:12:18 -0700488 public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
489 pw.println();
490
491 pw.print(prefix);
492 pw.print("Package: ");
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700493 pw.print(getPackageName());
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700494 pw.print(" UID: ");
495 pw.print(mPackageUid);
Makoto Onuki31459242016-03-22 11:12:18 -0700496 pw.println();
497
498 pw.print(prefix);
499 pw.print(" ");
500 pw.print("Calls: ");
501 pw.print(getApiCallCount(s));
502 pw.println();
503
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700504 // getApiCallCount() may have updated mLastKnownForegroundElapsedTime.
505 pw.print(prefix);
506 pw.print(" ");
507 pw.print("Last known FG: ");
508 pw.print(mLastKnownForegroundElapsedTime);
509 pw.println();
510
Makoto Onuki31459242016-03-22 11:12:18 -0700511 // This should be after getApiCallCount(), which may update it.
512 pw.print(prefix);
513 pw.print(" ");
514 pw.print("Last reset: [");
515 pw.print(mLastResetTime);
516 pw.print("] ");
517 pw.print(s.formatTime(mLastResetTime));
518 pw.println();
519
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700520 getPackageInfo().dump(s, pw, prefix + " ");
521 pw.println();
522
Makoto Onuki39686e82016-04-13 18:03:00 -0700523 pw.print(prefix);
524 pw.println(" Shortcuts:");
Makoto Onuki31459242016-03-22 11:12:18 -0700525 long totalBitmapSize = 0;
526 final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
527 final int size = shortcuts.size();
528 for (int i = 0; i < size; i++) {
529 final ShortcutInfo si = shortcuts.valueAt(i);
Makoto Onuki39686e82016-04-13 18:03:00 -0700530 pw.print(prefix);
531 pw.print(" ");
Makoto Onuki31459242016-03-22 11:12:18 -0700532 pw.println(si.toInsecureString());
533 if (si.getBitmapPath() != null) {
534 final long len = new File(si.getBitmapPath()).length();
Makoto Onuki39686e82016-04-13 18:03:00 -0700535 pw.print(prefix);
536 pw.print(" ");
Makoto Onuki31459242016-03-22 11:12:18 -0700537 pw.print("bitmap size=");
538 pw.println(len);
539
540 totalBitmapSize += len;
541 }
542 }
543 pw.print(prefix);
544 pw.print(" ");
545 pw.print("Total bitmap size: ");
546 pw.print(totalBitmapSize);
547 pw.print(" (");
548 pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize));
549 pw.println(")");
550 }
551
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700552 @Override
Makoto Onuki0acbb142016-03-22 17:02:57 -0700553 public void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
554 throws IOException, XmlPullParserException {
Makoto Onuki31459242016-03-22 11:12:18 -0700555 final int size = mShortcuts.size();
556
557 if (size == 0 && mApiCallCount == 0) {
558 return; // nothing to write.
559 }
560
561 out.startTag(null, TAG_ROOT);
562
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700563 ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
Makoto Onuki31459242016-03-22 11:12:18 -0700564 ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
565 ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
566 ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700567 getPackageInfo().saveToXml(out);
Makoto Onuki31459242016-03-22 11:12:18 -0700568
569 for (int j = 0; j < size; j++) {
Makoto Onuki0acbb142016-03-22 17:02:57 -0700570 saveShortcut(out, mShortcuts.valueAt(j), forBackup);
Makoto Onuki31459242016-03-22 11:12:18 -0700571 }
572
573 out.endTag(null, TAG_ROOT);
574 }
575
Makoto Onuki0acbb142016-03-22 17:02:57 -0700576 private static void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup)
Makoto Onuki31459242016-03-22 11:12:18 -0700577 throws IOException, XmlPullParserException {
Makoto Onuki0acbb142016-03-22 17:02:57 -0700578 if (forBackup) {
579 if (!si.isPinned()) {
580 return; // Backup only pinned icons.
581 }
582 }
Makoto Onuki31459242016-03-22 11:12:18 -0700583 out.startTag(null, TAG_SHORTCUT);
584 ShortcutService.writeAttr(out, ATTR_ID, si.getId());
585 // writeAttr(out, "package", si.getPackageName()); // not needed
586 ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent());
587 // writeAttr(out, "icon", si.getIcon()); // We don't save it.
588 ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
Makoto Onukie3ae7ec2016-03-29 15:45:25 -0700589 ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
Makoto Onuki31459242016-03-22 11:12:18 -0700590 ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
591 ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight());
592 ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
593 si.getLastChangedTimestamp());
Makoto Onuki0acbb142016-03-22 17:02:57 -0700594 if (forBackup) {
595 // Don't write icon information. Also drop the dynamic flag.
596 ShortcutService.writeAttr(out, ATTR_FLAGS,
597 si.getFlags() &
598 ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
599 | ShortcutInfo.FLAG_DYNAMIC));
600 } else {
601 ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
602 ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId());
603 ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
604 }
Makoto Onuki31459242016-03-22 11:12:18 -0700605
Makoto Onukib6d35232016-04-04 15:57:17 -0700606 {
Makoto Onukibe73a802016-04-15 14:46:35 -0700607 final Set<String> cat = si.getCategories();
Makoto Onukib6d35232016-04-04 15:57:17 -0700608 if (cat != null && cat.size() > 0) {
609 out.startTag(null, TAG_CATEGORIES);
610 XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
611 NAME_CATEGORIES, out);
612 out.endTag(null, TAG_CATEGORIES);
613 }
614 }
615
Makoto Onuki31459242016-03-22 11:12:18 -0700616 ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS,
617 si.getIntentPersistableExtras());
618 ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
619
620 out.endTag(null, TAG_SHORTCUT);
621 }
622
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700623 public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser,
624 XmlPullParser parser, boolean fromBackup)
Makoto Onuki31459242016-03-22 11:12:18 -0700625 throws IOException, XmlPullParserException {
626
627 final String packageName = ShortcutService.parseStringAttribute(parser,
628 ATTR_NAME);
629
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700630 final ShortcutPackage ret = new ShortcutPackage(s, shortcutUser,
631 shortcutUser.getUserId(), packageName);
Makoto Onuki31459242016-03-22 11:12:18 -0700632
633 ret.mDynamicShortcutCount =
634 ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
635 ret.mApiCallCount =
636 ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
637 ret.mLastResetTime =
638 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
639
640 final int outerDepth = parser.getDepth();
641 int type;
642 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
643 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
644 if (type != XmlPullParser.START_TAG) {
645 continue;
646 }
647 final int depth = parser.getDepth();
648 final String tag = parser.getName();
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700649 if (depth == outerDepth + 1) {
650 switch (tag) {
651 case ShortcutPackageInfo.TAG_ROOT:
Makoto Onuki2e210c42016-03-30 08:30:36 -0700652 ret.getPackageInfo().loadFromXml(parser, fromBackup);
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700653 continue;
654 case TAG_SHORTCUT:
Makoto Onuki4d36b3a2016-04-27 12:00:17 -0700655 final ShortcutInfo si = parseShortcut(parser, packageName,
656 shortcutUser.getUserId());
Makoto Onuki31459242016-03-22 11:12:18 -0700657
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700658 // Don't use addShortcut(), we don't need to save the icon.
659 ret.mShortcuts.put(si.getId(), si);
660 continue;
661 }
Makoto Onuki31459242016-03-22 11:12:18 -0700662 }
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700663 ShortcutService.warnForInvalidTag(depth, tag);
664 }
Makoto Onuki31459242016-03-22 11:12:18 -0700665 return ret;
666 }
667
Makoto Onukiabe84422016-04-07 09:41:19 -0700668 private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName,
669 @UserIdInt int userId) throws IOException, XmlPullParserException {
Makoto Onuki31459242016-03-22 11:12:18 -0700670 String id;
671 ComponentName activityComponent;
672 // Icon icon;
673 String title;
Makoto Onukie3ae7ec2016-03-29 15:45:25 -0700674 String text;
Makoto Onuki31459242016-03-22 11:12:18 -0700675 Intent intent;
676 PersistableBundle intentPersistableExtras = null;
677 int weight;
678 PersistableBundle extras = null;
679 long lastChangedTimestamp;
680 int flags;
681 int iconRes;
682 String bitmapPath;
Makoto Onukibe73a802016-04-15 14:46:35 -0700683 ArraySet<String> categories = null;
Makoto Onuki31459242016-03-22 11:12:18 -0700684
685 id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
686 activityComponent = ShortcutService.parseComponentNameAttribute(parser,
687 ATTR_ACTIVITY);
688 title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE);
Makoto Onukie3ae7ec2016-03-29 15:45:25 -0700689 text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT);
Makoto Onuki31459242016-03-22 11:12:18 -0700690 intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT);
691 weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT);
Makoto Onuki9da23fc2016-03-29 11:14:42 -0700692 lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
Makoto Onuki31459242016-03-22 11:12:18 -0700693 flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS);
694 iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES);
695 bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH);
696
697 final int outerDepth = parser.getDepth();
698 int type;
699 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
700 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
701 if (type != XmlPullParser.START_TAG) {
702 continue;
703 }
704 final int depth = parser.getDepth();
705 final String tag = parser.getName();
706 if (ShortcutService.DEBUG_LOAD) {
707 Slog.d(TAG, String.format(" depth=%d type=%d name=%s",
708 depth, type, tag));
709 }
710 switch (tag) {
711 case TAG_INTENT_EXTRAS:
712 intentPersistableExtras = PersistableBundle.restoreFromXml(parser);
713 continue;
714 case TAG_EXTRAS:
715 extras = PersistableBundle.restoreFromXml(parser);
716 continue;
Makoto Onukib6d35232016-04-04 15:57:17 -0700717 case TAG_CATEGORIES:
718 // This just contains string-array.
719 continue;
720 case TAG_STRING_ARRAY_XMLUTILS:
721 if (NAME_CATEGORIES.equals(ShortcutService.parseStringAttribute(parser,
722 ATTR_NAME_XMLUTILS))) {
Makoto Onukibe73a802016-04-15 14:46:35 -0700723 final String[] ar = XmlUtils.readThisStringArrayXml(
724 parser, TAG_STRING_ARRAY_XMLUTILS, null);
725 categories = new ArraySet<>(ar.length);
726 for (int i = 0; i < ar.length; i++) {
727 categories.add(ar[i]);
728 }
Makoto Onukib6d35232016-04-04 15:57:17 -0700729 }
730 continue;
Makoto Onuki31459242016-03-22 11:12:18 -0700731 }
732 throw ShortcutService.throwForInvalidTag(depth, tag);
733 }
Makoto Onukibe73a802016-04-15 14:46:35 -0700734
Makoto Onuki31459242016-03-22 11:12:18 -0700735 return new ShortcutInfo(
Makoto Onukib6d35232016-04-04 15:57:17 -0700736 userId, id, packageName, activityComponent, /* icon =*/ null, title, text,
Makoto Onukibe73a802016-04-15 14:46:35 -0700737 categories, intent,
Makoto Onuki31459242016-03-22 11:12:18 -0700738 intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
739 iconRes, bitmapPath);
740 }
Makoto Onuki2e210c42016-03-30 08:30:36 -0700741
742 @VisibleForTesting
743 List<ShortcutInfo> getAllShortcutsForTest() {
744 return new ArrayList<>(mShortcuts.values());
745 }
Makoto Onuki31459242016-03-22 11:12:18 -0700746}