ShortcutPackage.java revision c51b2876ec5c0af449469a0f76bb38c51cfcff04
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.server.pm; 17 18import android.annotation.NonNull; 19import android.annotation.Nullable; 20import android.annotation.UserIdInt; 21import 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 30import com.android.internal.annotations.VisibleForTesting; 31import com.android.internal.util.XmlUtils; 32 33import 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; 42import java.util.Set; 43import java.util.function.Predicate; 44 45/** 46 * Package information used by {@link ShortcutService}. 47 */ 48class ShortcutPackage extends ShortcutPackageItem { 49 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"; 55 private static final String TAG_CATEGORIES = "categories"; 56 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"; 64 private static final String ATTR_TEXT = "text"; 65 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 72 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 77 /** 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 97 private final int mPackageUid; 98 99 private long mLastKnownForegroundElapsedTime; 100 101 private ShortcutPackage(ShortcutUser shortcutUser, 102 int packageUserId, String packageName, ShortcutPackageInfo spi) { 103 super(shortcutUser, packageUserId, packageName, 104 spi != null ? spi : ShortcutPackageInfo.newEmpty()); 105 106 mPackageUid = shortcutUser.mService.injectGetPackageUid(packageName, packageUserId); 107 } 108 109 public ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName) { 110 this(shortcutUser, packageUserId, packageName, null); 111 } 112 113 @Override 114 public int getOwnerUserId() { 115 // For packages, always owner user == package user. 116 return getPackageUserId(); 117 } 118 119 public int getPackageUid() { 120 return mPackageUid; 121 } 122 123 /** 124 * Called when a shortcut is about to be published. At this point we know the publisher package 125 * exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so 126 * we do some initialization for the package. 127 */ 128 private void onShortcutPublish() { 129 // Make sure we have the version code for the app. We need the version code in 130 // handlePackageUpdated(). 131 if (getPackageInfo().getVersionCode() < 0) { 132 final ShortcutService s = mShortcutUser.mService; 133 134 final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId()); 135 if (ShortcutService.DEBUG) { 136 Slog.d(TAG, String.format("Package %s version = %d", getPackageName(), 137 versionCode)); 138 } 139 if (versionCode >= 0) { 140 getPackageInfo().setVersionCode(versionCode); 141 s.scheduleSaveUser(getOwnerUserId()); 142 } 143 } 144 } 145 146 @Override 147 protected void onRestoreBlocked() { 148 // Can't restore due to version/signature mismatch. Remove all shortcuts. 149 mShortcuts.clear(); 150 } 151 152 @Override 153 protected void onRestored() { 154 // Because some launchers may not have been restored (e.g. allowBackup=false), 155 // we need to re-calculate the pinned shortcuts. 156 refreshPinnedFlags(); 157 } 158 159 /** 160 * Note this does *not* provide a correct view to the calling launcher. 161 */ 162 @Nullable 163 public ShortcutInfo findShortcutById(String id) { 164 return mShortcuts.get(id); 165 } 166 167 private ShortcutInfo deleteShortcut(@NonNull String id) { 168 final ShortcutInfo shortcut = mShortcuts.remove(id); 169 if (shortcut != null) { 170 mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut); 171 shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED); 172 } 173 return shortcut; 174 } 175 176 void addShortcut(@NonNull ShortcutInfo newShortcut) { 177 deleteShortcut(newShortcut.getId()); 178 mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut); 179 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 ShortcutInfo newShortcut) { 188 189 onShortcutPublish(); 190 191 newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); 192 193 final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId()); 194 195 final boolean wasPinned; 196 final int newDynamicCount; 197 198 if (oldShortcut == null) { 199 wasPinned = false; 200 newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut. 201 } else { 202 wasPinned = oldShortcut.isPinned(); 203 if (oldShortcut.isDynamic()) { 204 newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut. 205 } else { 206 newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut. 207 } 208 } 209 210 // Make sure there's still room. 211 mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount); 212 213 // Okay, make it dynamic and add. 214 if (wasPinned) { 215 newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); 216 } 217 218 addShortcut(newShortcut); 219 mDynamicShortcutCount = newDynamicCount; 220 } 221 222 /** 223 * Remove all shortcuts that aren't pinned nor dynamic. 224 */ 225 private void removeOrphans() { 226 ArrayList<String> removeList = null; // Lazily initialize. 227 228 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 229 final ShortcutInfo si = mShortcuts.valueAt(i); 230 231 if (si.isPinned() || si.isDynamic()) continue; 232 233 if (removeList == null) { 234 removeList = new ArrayList<>(); 235 } 236 removeList.add(si.getId()); 237 } 238 if (removeList != null) { 239 for (int i = removeList.size() - 1; i >= 0; i--) { 240 deleteShortcut(removeList.get(i)); 241 } 242 } 243 } 244 245 /** 246 * Remove all dynamic shortcuts. 247 */ 248 public void deleteAllDynamicShortcuts() { 249 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 250 mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC); 251 } 252 removeOrphans(); 253 mDynamicShortcutCount = 0; 254 } 255 256 /** 257 * Remove a dynamic shortcut by ID. 258 */ 259 public void deleteDynamicWithId(@NonNull String shortcutId) { 260 final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId); 261 262 if (oldShortcut == null) { 263 return; 264 } 265 if (oldShortcut.isDynamic()) { 266 mDynamicShortcutCount--; 267 } 268 if (oldShortcut.isPinned()) { 269 oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC); 270 } else { 271 deleteShortcut(shortcutId); 272 } 273 } 274 275 /** 276 * Called after a launcher updates the pinned set. For each shortcut in this package, 277 * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it. 278 * 279 * <p>Then remove all shortcuts that are not dynamic and no longer pinned either. 280 */ 281 public void refreshPinnedFlags() { 282 // First, un-pin all shortcuts 283 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 284 mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED); 285 } 286 287 // Then, for the pinned set for each launcher, set the pin flag one by one. 288 mShortcutUser.mService.getUserShortcutsLocked(getPackageUserId()) 289 .forAllLaunchers(launcherShortcuts -> { 290 final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds( 291 getPackageName(), getPackageUserId()); 292 293 if (pinned == null || pinned.size() == 0) { 294 return; 295 } 296 for (int i = pinned.size() - 1; i >= 0; i--) { 297 final String id = pinned.valueAt(i); 298 final ShortcutInfo si = mShortcuts.get(id); 299 if (si == null) { 300 // 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; 305 } 306 si.addFlags(ShortcutInfo.FLAG_PINNED); 307 } 308 }); 309 310 // Lastly, remove the ones that are no longer pinned nor dynamic. 311 removeOrphans(); 312 } 313 314 /** 315 * Number of calls that the caller has made, since the last reset. 316 * 317 * <p>This takes care of the resetting the counter for foreground apps as well as after 318 * locale changes. 319 */ 320 public int getApiCallCount() { 321 mShortcutUser.resetThrottlingIfNeeded(); 322 323 final ShortcutService s = mShortcutUser.mService; 324 325 // Reset the counter if: 326 // - the package is in foreground now. 327 // - the package is *not* in foreground now, but was in foreground at some point 328 // since the previous time it had been. 329 if (s.isUidForegroundLocked(mPackageUid) 330 || mLastKnownForegroundElapsedTime 331 < s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) { 332 mLastKnownForegroundElapsedTime = s.injectElapsedRealtime(); 333 resetRateLimiting(); 334 } 335 336 // Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount, 337 // but we just can't return 0 at this point, because we may have to update 338 // mLastResetTime. 339 340 final long last = s.getLastResetTimeLocked(); 341 342 final long now = s.injectCurrentTimeMillis(); 343 if (ShortcutService.isClockValid(now) && mLastResetTime > now) { 344 Slog.w(TAG, "Clock rewound"); 345 // Clock rewound. 346 mLastResetTime = now; 347 mApiCallCount = 0; 348 return mApiCallCount; 349 } 350 351 // If not reset yet, then reset. 352 if (mLastResetTime < last) { 353 if (ShortcutService.DEBUG) { 354 Slog.d(TAG, String.format("%s: last reset=%d, now=%d, last=%d: resetting", 355 getPackageName(), mLastResetTime, now, last)); 356 } 357 mApiCallCount = 0; 358 mLastResetTime = last; 359 } 360 return mApiCallCount; 361 } 362 363 /** 364 * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount} 365 * and return true. Otherwise just return false. 366 * 367 * <p>This takes care of the resetting the counter for foreground apps as well as after 368 * locale changes, which is done internally by {@link #getApiCallCount}. 369 */ 370 public boolean tryApiCall() { 371 final ShortcutService s = mShortcutUser.mService; 372 373 if (getApiCallCount() >= s.mMaxUpdatesPerInterval) { 374 return false; 375 } 376 mApiCallCount++; 377 s.scheduleSaveUser(getOwnerUserId()); 378 return true; 379 } 380 381 public void resetRateLimiting() { 382 if (ShortcutService.DEBUG) { 383 Slog.d(TAG, "resetRateLimiting: " + getPackageName()); 384 } 385 if (mApiCallCount > 0) { 386 mApiCallCount = 0; 387 mShortcutUser.mService.scheduleSaveUser(getOwnerUserId()); 388 } 389 } 390 391 public void resetRateLimitingForCommandLineNoSaving() { 392 mApiCallCount = 0; 393 mLastResetTime = 0; 394 } 395 396 /** 397 * Find all shortcuts that match {@code query}. 398 */ 399 public void findAll(@NonNull List<ShortcutInfo> result, 400 @Nullable Predicate<ShortcutInfo> query, int cloneFlag) { 401 findAll(result, query, cloneFlag, null, 0); 402 } 403 404 /** 405 * Find all shortcuts that match {@code query}. 406 * 407 * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned 408 * by the calling launcher will not be included in the result, and also "isPinned" will be 409 * adjusted for the caller too. 410 */ 411 public void findAll(@NonNull List<ShortcutInfo> result, 412 @Nullable Predicate<ShortcutInfo> query, int cloneFlag, 413 @Nullable String callingLauncher, int launcherUserId) { 414 if (getPackageInfo().isShadow()) { 415 // Restored and the app not installed yet, so don't return any. 416 return; 417 } 418 419 final ShortcutService s = mShortcutUser.mService; 420 421 // Set of pinned shortcuts by the calling launcher. 422 final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null 423 : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) 424 .getPinnedShortcutIds(getPackageName(), getPackageUserId()); 425 426 for (int i = 0; i < mShortcuts.size(); i++) { 427 final ShortcutInfo si = mShortcuts.valueAt(i); 428 429 // If it's called by non-launcher (i.e. publisher, always include -> true. 430 // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned 431 // it. 432 final boolean isPinnedByCaller = (callingLauncher == null) 433 || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId())); 434 if (!si.isDynamic()) { 435 if (!si.isPinned()) { 436 s.wtf("Shortcut not pinned: package " + getPackageName() 437 + ", user=" + getPackageUserId() + ", id=" + si.getId()); 438 continue; 439 } 440 if (!isPinnedByCaller) { 441 continue; 442 } 443 } 444 final ShortcutInfo clone = si.clone(cloneFlag); 445 // Fix up isPinned for the caller. Note we need to do it before the "test" callback, 446 // since it may check isPinned. 447 if (!isPinnedByCaller) { 448 clone.clearFlags(ShortcutInfo.FLAG_PINNED); 449 } 450 if (query == null || query.test(clone)) { 451 result.add(clone); 452 } 453 } 454 } 455 456 public void resetThrottling() { 457 mApiCallCount = 0; 458 } 459 460 /** 461 * Return the filenames (excluding path names) of icon bitmap files from this package. 462 */ 463 public ArraySet<String> getUsedBitmapFiles() { 464 final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size()); 465 466 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 467 final ShortcutInfo si = mShortcuts.valueAt(i); 468 if (si.getBitmapPath() != null) { 469 usedFiles.add(getFileName(si.getBitmapPath())); 470 } 471 } 472 return usedFiles; 473 } 474 475 private static String getFileName(@NonNull String path) { 476 final int sep = path.lastIndexOf(File.separatorChar); 477 if (sep == -1) { 478 return path; 479 } else { 480 return path.substring(sep + 1); 481 } 482 } 483 484 /** 485 * Called when the package is updated. If there are shortcuts with resource icons, update 486 * their timestamps. 487 */ 488 public void handlePackageUpdated(int newVersionCode) { 489 if (getPackageInfo().getVersionCode() >= newVersionCode) { 490 // Version hasn't changed; nothing to do. 491 return; 492 } 493 if (ShortcutService.DEBUG) { 494 Slog.d(TAG, String.format("Package %s updated, version %d -> %d", getPackageName(), 495 getPackageInfo().getVersionCode(), newVersionCode)); 496 } 497 498 getPackageInfo().setVersionCode(newVersionCode); 499 500 final ShortcutService s = mShortcutUser.mService; 501 502 boolean changed = false; 503 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 504 final ShortcutInfo si = mShortcuts.valueAt(i); 505 506 if (si.hasIconResource()) { 507 changed = true; 508 si.setTimestamp(s.injectCurrentTimeMillis()); 509 } 510 } 511 if (changed) { 512 // This will send a notification to the launcher, and also save . 513 s.packageShortcutsChanged(getPackageName(), getPackageUserId()); 514 } else { 515 // Still save the version code. 516 s.scheduleSaveUser(getPackageUserId()); 517 } 518 } 519 520 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 521 pw.println(); 522 523 pw.print(prefix); 524 pw.print("Package: "); 525 pw.print(getPackageName()); 526 pw.print(" UID: "); 527 pw.print(mPackageUid); 528 pw.println(); 529 530 pw.print(prefix); 531 pw.print(" "); 532 pw.print("Calls: "); 533 pw.print(getApiCallCount()); 534 pw.println(); 535 536 // getApiCallCount() may have updated mLastKnownForegroundElapsedTime. 537 pw.print(prefix); 538 pw.print(" "); 539 pw.print("Last known FG: "); 540 pw.print(mLastKnownForegroundElapsedTime); 541 pw.println(); 542 543 // This should be after getApiCallCount(), which may update it. 544 pw.print(prefix); 545 pw.print(" "); 546 pw.print("Last reset: ["); 547 pw.print(mLastResetTime); 548 pw.print("] "); 549 pw.print(ShortcutService.formatTime(mLastResetTime)); 550 pw.println(); 551 552 getPackageInfo().dump(pw, prefix + " "); 553 pw.println(); 554 555 pw.print(prefix); 556 pw.println(" Shortcuts:"); 557 long totalBitmapSize = 0; 558 final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts; 559 final int size = shortcuts.size(); 560 for (int i = 0; i < size; i++) { 561 final ShortcutInfo si = shortcuts.valueAt(i); 562 pw.print(prefix); 563 pw.print(" "); 564 pw.println(si.toInsecureString()); 565 if (si.getBitmapPath() != null) { 566 final long len = new File(si.getBitmapPath()).length(); 567 pw.print(prefix); 568 pw.print(" "); 569 pw.print("bitmap size="); 570 pw.println(len); 571 572 totalBitmapSize += len; 573 } 574 } 575 pw.print(prefix); 576 pw.print(" "); 577 pw.print("Total bitmap size: "); 578 pw.print(totalBitmapSize); 579 pw.print(" ("); 580 pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize)); 581 pw.println(")"); 582 } 583 584 @Override 585 public void saveToXml(@NonNull XmlSerializer out, boolean forBackup) 586 throws IOException, XmlPullParserException { 587 final int size = mShortcuts.size(); 588 589 if (size == 0 && mApiCallCount == 0) { 590 return; // nothing to write. 591 } 592 593 out.startTag(null, TAG_ROOT); 594 595 ShortcutService.writeAttr(out, ATTR_NAME, getPackageName()); 596 ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount); 597 ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); 598 ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); 599 getPackageInfo().saveToXml(out); 600 601 for (int j = 0; j < size; j++) { 602 saveShortcut(out, mShortcuts.valueAt(j), forBackup); 603 } 604 605 out.endTag(null, TAG_ROOT); 606 } 607 608 private static void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup) 609 throws IOException, XmlPullParserException { 610 if (forBackup) { 611 if (!si.isPinned()) { 612 return; // Backup only pinned icons. 613 } 614 } 615 out.startTag(null, TAG_SHORTCUT); 616 ShortcutService.writeAttr(out, ATTR_ID, si.getId()); 617 // writeAttr(out, "package", si.getPackageName()); // not needed 618 ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent()); 619 // writeAttr(out, "icon", si.getIcon()); // We don't save it. 620 ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); 621 ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); 622 ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras()); 623 ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight()); 624 ShortcutService.writeAttr(out, ATTR_TIMESTAMP, 625 si.getLastChangedTimestamp()); 626 if (forBackup) { 627 // Don't write icon information. Also drop the dynamic flag. 628 ShortcutService.writeAttr(out, ATTR_FLAGS, 629 si.getFlags() & 630 ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES 631 | ShortcutInfo.FLAG_DYNAMIC)); 632 } else { 633 ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags()); 634 ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId()); 635 ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); 636 } 637 638 { 639 final Set<String> cat = si.getCategories(); 640 if (cat != null && cat.size() > 0) { 641 out.startTag(null, TAG_CATEGORIES); 642 XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]), 643 NAME_CATEGORIES, out); 644 out.endTag(null, TAG_CATEGORIES); 645 } 646 } 647 648 ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS, 649 si.getIntentPersistableExtras()); 650 ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); 651 652 out.endTag(null, TAG_SHORTCUT); 653 } 654 655 public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser, 656 XmlPullParser parser, boolean fromBackup) 657 throws IOException, XmlPullParserException { 658 659 final String packageName = ShortcutService.parseStringAttribute(parser, 660 ATTR_NAME); 661 662 final ShortcutPackage ret = new ShortcutPackage(shortcutUser, 663 shortcutUser.getUserId(), packageName); 664 665 ret.mDynamicShortcutCount = 666 ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT); 667 ret.mApiCallCount = 668 ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); 669 ret.mLastResetTime = 670 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); 671 672 final int outerDepth = parser.getDepth(); 673 int type; 674 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 675 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 676 if (type != XmlPullParser.START_TAG) { 677 continue; 678 } 679 final int depth = parser.getDepth(); 680 final String tag = parser.getName(); 681 if (depth == outerDepth + 1) { 682 switch (tag) { 683 case ShortcutPackageInfo.TAG_ROOT: 684 ret.getPackageInfo().loadFromXml(parser, fromBackup); 685 continue; 686 case TAG_SHORTCUT: 687 final ShortcutInfo si = parseShortcut(parser, packageName, 688 shortcutUser.getUserId()); 689 690 // Don't use addShortcut(), we don't need to save the icon. 691 ret.mShortcuts.put(si.getId(), si); 692 continue; 693 } 694 } 695 ShortcutService.warnForInvalidTag(depth, tag); 696 } 697 return ret; 698 } 699 700 private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName, 701 @UserIdInt int userId) throws IOException, XmlPullParserException { 702 String id; 703 ComponentName activityComponent; 704 // Icon icon; 705 String title; 706 String text; 707 Intent intent; 708 PersistableBundle intentPersistableExtras = null; 709 int weight; 710 PersistableBundle extras = null; 711 long lastChangedTimestamp; 712 int flags; 713 int iconRes; 714 String bitmapPath; 715 ArraySet<String> categories = null; 716 717 id = ShortcutService.parseStringAttribute(parser, ATTR_ID); 718 activityComponent = ShortcutService.parseComponentNameAttribute(parser, 719 ATTR_ACTIVITY); 720 title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); 721 text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT); 722 intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT); 723 weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT); 724 lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP); 725 flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS); 726 iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES); 727 bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH); 728 729 final int outerDepth = parser.getDepth(); 730 int type; 731 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 732 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 733 if (type != XmlPullParser.START_TAG) { 734 continue; 735 } 736 final int depth = parser.getDepth(); 737 final String tag = parser.getName(); 738 if (ShortcutService.DEBUG_LOAD) { 739 Slog.d(TAG, String.format(" depth=%d type=%d name=%s", 740 depth, type, tag)); 741 } 742 switch (tag) { 743 case TAG_INTENT_EXTRAS: 744 intentPersistableExtras = PersistableBundle.restoreFromXml(parser); 745 continue; 746 case TAG_EXTRAS: 747 extras = PersistableBundle.restoreFromXml(parser); 748 continue; 749 case TAG_CATEGORIES: 750 // This just contains string-array. 751 continue; 752 case TAG_STRING_ARRAY_XMLUTILS: 753 if (NAME_CATEGORIES.equals(ShortcutService.parseStringAttribute(parser, 754 ATTR_NAME_XMLUTILS))) { 755 final String[] ar = XmlUtils.readThisStringArrayXml( 756 parser, TAG_STRING_ARRAY_XMLUTILS, null); 757 categories = new ArraySet<>(ar.length); 758 for (int i = 0; i < ar.length; i++) { 759 categories.add(ar[i]); 760 } 761 } 762 continue; 763 } 764 throw ShortcutService.throwForInvalidTag(depth, tag); 765 } 766 767 return new ShortcutInfo( 768 userId, id, packageName, activityComponent, /* icon =*/ null, title, text, 769 categories, intent, 770 intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, 771 iconRes, bitmapPath); 772 } 773 774 @VisibleForTesting 775 List<ShortcutInfo> getAllShortcutsForTest() { 776 return new ArrayList<>(mShortcuts.values()); 777 } 778} 779