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