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(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); 107 } 108 109 public ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser, 110 int packageUserId, String packageName) { 111 this(s, shortcutUser, packageUserId, packageName, null); 112 } 113 114 @Override 115 public int getOwnerUserId() { 116 // For packages, always owner user == package user. 117 return getPackageUserId(); 118 } 119 120 public int getPackageUid() { 121 return mPackageUid; 122 } 123 124 /** 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 145 @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 158 /** 159 * Note this does *not* provide a correct view to the calling launcher. 160 */ 161 @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) { 170 s.removeIcon(getPackageUserId(), shortcut); 171 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()); 178 s.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 ShortcutService s, 188 @NonNull ShortcutInfo newShortcut) { 189 190 onShortcutPublish(s); 191 192 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. 289 s.getUserShortcutsLocked(getPackageUserId()).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(s); 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(@NonNull ShortcutService s) { 321 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 338 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. 364 * 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}. 367 */ 368 public boolean tryApiCall(@NonNull ShortcutService s) { 369 if (getApiCallCount(s) >= s.mMaxUpdatesPerInterval) { 370 return false; 371 } 372 mApiCallCount++; 373 s.scheduleSaveUser(getOwnerUserId()); 374 return true; 375 } 376 377 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() { 388 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, 396 @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, 408 @Nullable Predicate<ShortcutInfo> query, int cloneFlag, 409 @Nullable String callingLauncher, int launcherUserId) { 410 if (getPackageInfo().isShadow()) { 411 // Restored and the app not installed yet, so don't return any. 412 return; 413 } 414 415 // Set of pinned shortcuts by the calling launcher. 416 final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null 417 : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) 418 .getPinnedShortcutIds(getPackageName(), getPackageUserId()); 419 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()) { 430 s.wtf("Shortcut not pinned: package " + getPackageName() 431 + ", user=" + getPackageUserId() + ", id=" + si.getId()); 432 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 454 /** 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 488 public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) { 489 pw.println(); 490 491 pw.print(prefix); 492 pw.print("Package: "); 493 pw.print(getPackageName()); 494 pw.print(" UID: "); 495 pw.print(mPackageUid); 496 pw.println(); 497 498 pw.print(prefix); 499 pw.print(" "); 500 pw.print("Calls: "); 501 pw.print(getApiCallCount(s)); 502 pw.println(); 503 504 // 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 511 // 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 520 getPackageInfo().dump(s, pw, prefix + " "); 521 pw.println(); 522 523 pw.print(prefix); 524 pw.println(" Shortcuts:"); 525 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); 530 pw.print(prefix); 531 pw.print(" "); 532 pw.println(si.toInsecureString()); 533 if (si.getBitmapPath() != null) { 534 final long len = new File(si.getBitmapPath()).length(); 535 pw.print(prefix); 536 pw.print(" "); 537 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 552 @Override 553 public void saveToXml(@NonNull XmlSerializer out, boolean forBackup) 554 throws IOException, XmlPullParserException { 555 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 563 ShortcutService.writeAttr(out, ATTR_NAME, getPackageName()); 564 ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount); 565 ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); 566 ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); 567 getPackageInfo().saveToXml(out); 568 569 for (int j = 0; j < size; j++) { 570 saveShortcut(out, mShortcuts.valueAt(j), forBackup); 571 } 572 573 out.endTag(null, TAG_ROOT); 574 } 575 576 private static void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup) 577 throws IOException, XmlPullParserException { 578 if (forBackup) { 579 if (!si.isPinned()) { 580 return; // Backup only pinned icons. 581 } 582 } 583 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()); 589 ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); 590 ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras()); 591 ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight()); 592 ShortcutService.writeAttr(out, ATTR_TIMESTAMP, 593 si.getLastChangedTimestamp()); 594 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 } 605 606 { 607 final Set<String> cat = si.getCategories(); 608 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 616 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 623 public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser, 624 XmlPullParser parser, boolean fromBackup) 625 throws IOException, XmlPullParserException { 626 627 final String packageName = ShortcutService.parseStringAttribute(parser, 628 ATTR_NAME); 629 630 final ShortcutPackage ret = new ShortcutPackage(s, shortcutUser, 631 shortcutUser.getUserId(), packageName); 632 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(); 649 if (depth == outerDepth + 1) { 650 switch (tag) { 651 case ShortcutPackageInfo.TAG_ROOT: 652 ret.getPackageInfo().loadFromXml(parser, fromBackup); 653 continue; 654 case TAG_SHORTCUT: 655 final ShortcutInfo si = parseShortcut(parser, packageName, 656 shortcutUser.getUserId()); 657 658 // Don't use addShortcut(), we don't need to save the icon. 659 ret.mShortcuts.put(si.getId(), si); 660 continue; 661 } 662 } 663 ShortcutService.warnForInvalidTag(depth, tag); 664 } 665 return ret; 666 } 667 668 private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName, 669 @UserIdInt int userId) throws IOException, XmlPullParserException { 670 String id; 671 ComponentName activityComponent; 672 // Icon icon; 673 String title; 674 String text; 675 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; 683 ArraySet<String> categories = null; 684 685 id = ShortcutService.parseStringAttribute(parser, ATTR_ID); 686 activityComponent = ShortcutService.parseComponentNameAttribute(parser, 687 ATTR_ACTIVITY); 688 title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); 689 text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT); 690 intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT); 691 weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT); 692 lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP); 693 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; 717 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))) { 723 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 } 729 } 730 continue; 731 } 732 throw ShortcutService.throwForInvalidTag(depth, tag); 733 } 734 735 return new ShortcutInfo( 736 userId, id, packageName, activityComponent, /* icon =*/ null, title, text, 737 categories, intent, 738 intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, 739 iconRes, bitmapPath); 740 } 741 742 @VisibleForTesting 743 List<ShortcutInfo> getAllShortcutsForTest() { 744 return new ArrayList<>(mShortcuts.values()); 745 } 746} 747