ShortcutPackage.java revision eddbfecb8dd751161339a9ed16d07ce2e108a575
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.PackageInfo; 24import android.content.pm.ShortcutInfo; 25import android.os.PersistableBundle; 26import android.text.format.Formatter; 27import android.util.ArrayMap; 28import android.util.ArraySet; 29import android.util.Slog; 30 31import com.android.internal.annotations.VisibleForTesting; 32import com.android.internal.util.Preconditions; 33import com.android.internal.util.XmlUtils; 34 35import org.xmlpull.v1.XmlPullParser; 36import org.xmlpull.v1.XmlPullParserException; 37import org.xmlpull.v1.XmlSerializer; 38 39import java.io.File; 40import java.io.IOException; 41import java.io.PrintWriter; 42import java.util.ArrayList; 43import java.util.List; 44import java.util.Set; 45import java.util.function.Predicate; 46 47/** 48 * Package information used by {@link ShortcutService}. 49 * User information used by {@link ShortcutService}. 50 * 51 * All methods should be guarded by {@code #mShortcutUser.mService.mLock}. 52 * 53 * TODO Max dynamic shortcuts cap should be per activity. 54 */ 55class ShortcutPackage extends ShortcutPackageItem { 56 private static final String TAG = ShortcutService.TAG; 57 58 static final String TAG_ROOT = "package"; 59 private static final String TAG_INTENT_EXTRAS = "intent-extras"; 60 private static final String TAG_EXTRAS = "extras"; 61 private static final String TAG_SHORTCUT = "shortcut"; 62 private static final String TAG_CATEGORIES = "categories"; 63 64 private static final String ATTR_NAME = "name"; 65 private static final String ATTR_CALL_COUNT = "call-count"; 66 private static final String ATTR_LAST_RESET = "last-reset"; 67 private static final String ATTR_ID = "id"; 68 private static final String ATTR_ACTIVITY = "activity"; 69 private static final String ATTR_TITLE = "title"; 70 private static final String ATTR_TITLE_RES_ID = "titleid"; 71 private static final String ATTR_TEXT = "text"; 72 private static final String ATTR_TEXT_RES_ID = "textid"; 73 private static final String ATTR_DISABLED_MESSAGE = "dmessage"; 74 private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid"; 75 private static final String ATTR_INTENT = "intent"; 76 private static final String ATTR_RANK = "rank"; 77 private static final String ATTR_TIMESTAMP = "timestamp"; 78 private static final String ATTR_FLAGS = "flags"; 79 private static final String ATTR_ICON_RES = "icon-res"; 80 private static final String ATTR_BITMAP_PATH = "bitmap-path"; 81 82 private static final String NAME_CATEGORIES = "categories"; 83 84 private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array"; 85 private static final String ATTR_NAME_XMLUTILS = "name"; 86 87 /** 88 * All the shortcuts from the package, keyed on IDs. 89 */ 90 final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>(); 91 92 /** 93 * # of times the package has called rate-limited APIs. 94 */ 95 private int mApiCallCount; 96 97 /** 98 * When {@link #mApiCallCount} was reset last time. 99 */ 100 private long mLastResetTime; 101 102 private final int mPackageUid; 103 104 private long mLastKnownForegroundElapsedTime; 105 106 private ShortcutPackage(ShortcutUser shortcutUser, 107 int packageUserId, String packageName, ShortcutPackageInfo spi) { 108 super(shortcutUser, packageUserId, packageName, 109 spi != null ? spi : ShortcutPackageInfo.newEmpty()); 110 111 mPackageUid = shortcutUser.mService.injectGetPackageUid(packageName, packageUserId); 112 } 113 114 public ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName) { 115 this(shortcutUser, packageUserId, packageName, null); 116 } 117 118 @Override 119 public int getOwnerUserId() { 120 // For packages, always owner user == package user. 121 return getPackageUserId(); 122 } 123 124 public int getPackageUid() { 125 return mPackageUid; 126 } 127 128 /** 129 * Called when a shortcut is about to be published. At this point we know the publisher package 130 * exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so 131 * we do some initialization for the package. 132 */ 133 private void ensurePackageVersionInfo() { 134 // Make sure we have the version code for the app. We need the version code in 135 // handlePackageUpdated(). 136 if (getPackageInfo().getVersionCode() < 0) { 137 final ShortcutService s = mShortcutUser.mService; 138 139 final PackageInfo pi = s.getPackageInfo(getPackageName(), getOwnerUserId()); 140 if (pi != null) { 141 if (ShortcutService.DEBUG) { 142 Slog.d(TAG, String.format("Package %s version = %d", getPackageName(), 143 pi.versionCode)); 144 } 145 getPackageInfo().updateVersionInfo(pi); 146 s.scheduleSaveUser(getOwnerUserId()); 147 } 148 } 149 } 150 151 @Override 152 protected void onRestoreBlocked() { 153 // Can't restore due to version/signature mismatch. Remove all shortcuts. 154 mShortcuts.clear(); 155 } 156 157 @Override 158 protected void onRestored() { 159 // Because some launchers may not have been restored (e.g. allowBackup=false), 160 // we need to re-calculate the pinned shortcuts. 161 refreshPinnedFlags(); 162 } 163 164 /** 165 * Note this does *not* provide a correct view to the calling launcher. 166 */ 167 @Nullable 168 public ShortcutInfo findShortcutById(String id) { 169 return mShortcuts.get(id); 170 } 171 172 private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) { 173 if (shortcut != null && shortcut.isImmutable()) { 174 throw new IllegalArgumentException( 175 "Manifest shortcut ID=" + shortcut.getId() 176 + " may not be manipulated via APIs"); 177 } 178 } 179 180 private void ensureNotImmutable(@NonNull String id) { 181 ensureNotImmutable(mShortcuts.get(id)); 182 } 183 184 public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) { 185 for (int i = shortcutIds.size() - 1; i >= 0; i--) { 186 ensureNotImmutable(shortcutIds.get(i)); 187 } 188 } 189 190 public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) { 191 for (int i = shortcuts.size() - 1; i >= 0; i--) { 192 ensureNotImmutable(shortcuts.get(i).getId()); 193 } 194 } 195 196 private ShortcutInfo deleteShortcutInner(@NonNull String id) { 197 final ShortcutInfo shortcut = mShortcuts.remove(id); 198 if (shortcut != null) { 199 mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut); 200 shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED 201 | ShortcutInfo.FLAG_MANIFEST); 202 } 203 return shortcut; 204 } 205 206 private void addShortcutInner(@NonNull ShortcutInfo newShortcut) { 207 deleteShortcutInner(newShortcut.getId()); 208 mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut); 209 mShortcuts.put(newShortcut.getId(), newShortcut); 210 } 211 212 /** 213 * Add a shortcut, or update one with the same ID, with taking over existing flags. 214 * 215 * It checks the max number of dynamic shortcuts. 216 */ 217 public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) { 218 219 Preconditions.checkArgument(newShortcut.isEnabled(), 220 "add/setDynamicShortcuts() cannot publish disabled shortcuts"); 221 222 ensurePackageVersionInfo(); 223 224 newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); 225 226 final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId()); 227 228 final boolean wasPinned; 229 230 if (oldShortcut == null) { 231 wasPinned = false; 232 } else { 233 // It's an update case. 234 // Make sure the target is updatable. (i.e. should be mutable.) 235 oldShortcut.ensureUpdatableWith(newShortcut); 236 237 wasPinned = oldShortcut.isPinned(); 238 if (!oldShortcut.isEnabled()) { 239 newShortcut.addFlags(ShortcutInfo.FLAG_DISABLED); 240 } 241 } 242 243 // TODO Check max dynamic count. 244 // mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount); 245 246 // Okay, make it dynamic and add. 247 if (wasPinned) { 248 newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); 249 } 250 251 addShortcutInner(newShortcut); 252 } 253 254 /** 255 * Remove all shortcuts that aren't pinned nor dynamic. 256 */ 257 private void removeOrphans() { 258 ArrayList<String> removeList = null; // Lazily initialize. 259 260 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 261 final ShortcutInfo si = mShortcuts.valueAt(i); 262 263 if (si.isAlive()) continue; 264 265 if (removeList == null) { 266 removeList = new ArrayList<>(); 267 } 268 removeList.add(si.getId()); 269 } 270 if (removeList != null) { 271 for (int i = removeList.size() - 1; i >= 0; i--) { 272 deleteShortcutInner(removeList.get(i)); 273 } 274 } 275 } 276 277 /** 278 * Remove all dynamic shortcuts. 279 */ 280 public void deleteAllDynamicShortcuts() { 281 boolean changed = false; 282 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 283 final ShortcutInfo si = mShortcuts.valueAt(i); 284 if (si.isDynamic()) { 285 changed = true; 286 si.clearFlags(ShortcutInfo.FLAG_DYNAMIC); 287 } 288 } 289 if (changed) { 290 removeOrphans(); 291 } 292 } 293 294 /** 295 * Remove a dynamic shortcut by ID. 296 */ 297 public void deleteDynamicWithId(@NonNull String shortcutId) { 298 deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false); 299 } 300 301 public void disableWithId(@NonNull String shortcutId, String disabledMessage, 302 int disabledMessageResId, boolean overrideImmutable) { 303 final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true, 304 overrideImmutable); 305 306 if (disabled != null) { 307 if (disabledMessage != null) { 308 disabled.setDisabledMessage(disabledMessage); 309 } else if (disabledMessageResId != 0) { 310 disabled.setDisabledMessageResId(disabledMessageResId); 311 } 312 } 313 } 314 315 @Nullable 316 private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable, 317 boolean overrideImmutable) { 318 final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId); 319 320 if (oldShortcut == null || !oldShortcut.isEnabled()) { 321 return null; // Doesn't exist or already disabled. 322 } 323 if (!overrideImmutable) { 324 ensureNotImmutable(oldShortcut); 325 } 326 if (oldShortcut.isPinned()) { 327 oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST); 328 if (disable) { 329 oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED); 330 } 331 return oldShortcut; 332 } else { 333 deleteShortcutInner(shortcutId); 334 return null; 335 } 336 } 337 338 public void enableWithId(@NonNull String shortcutId) { 339 final ShortcutInfo shortcut = mShortcuts.get(shortcutId); 340 if (shortcut != null) { 341 ensureNotImmutable(shortcut); 342 shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED); 343 } 344 } 345 346 /** 347 * Called after a launcher updates the pinned set. For each shortcut in this package, 348 * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it. 349 * 350 * <p>Then remove all shortcuts that are not dynamic and no longer pinned either. 351 */ 352 public void refreshPinnedFlags() { 353 // First, un-pin all shortcuts 354 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 355 mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED); 356 } 357 358 // Then, for the pinned set for each launcher, set the pin flag one by one. 359 mShortcutUser.mService.getUserShortcutsLocked(getPackageUserId()) 360 .forAllLaunchers(launcherShortcuts -> { 361 final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds( 362 getPackageName(), getPackageUserId()); 363 364 if (pinned == null || pinned.size() == 0) { 365 return; 366 } 367 for (int i = pinned.size() - 1; i >= 0; i--) { 368 final String id = pinned.valueAt(i); 369 final ShortcutInfo si = mShortcuts.get(id); 370 if (si == null) { 371 // This happens if a launcher pinned shortcuts from this package, then backup& 372 // restored, but this package doesn't allow backing up. 373 // In that case the launcher ends up having a dangling pinned shortcuts. 374 // That's fine, when the launcher is restored, we'll fix it. 375 continue; 376 } 377 si.addFlags(ShortcutInfo.FLAG_PINNED); 378 } 379 }); 380 381 // Lastly, remove the ones that are no longer pinned nor dynamic. 382 removeOrphans(); 383 } 384 385 /** 386 * Number of calls that the caller has made, since the last reset. 387 * 388 * <p>This takes care of the resetting the counter for foreground apps as well as after 389 * locale changes. 390 */ 391 public int getApiCallCount() { 392 mShortcutUser.resetThrottlingIfNeeded(); 393 394 final ShortcutService s = mShortcutUser.mService; 395 396 // Reset the counter if: 397 // - the package is in foreground now. 398 // - the package is *not* in foreground now, but was in foreground at some point 399 // since the previous time it had been. 400 if (s.isUidForegroundLocked(mPackageUid) 401 || mLastKnownForegroundElapsedTime 402 < s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) { 403 mLastKnownForegroundElapsedTime = s.injectElapsedRealtime(); 404 resetRateLimiting(); 405 } 406 407 // Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount, 408 // but we just can't return 0 at this point, because we may have to update 409 // mLastResetTime. 410 411 final long last = s.getLastResetTimeLocked(); 412 413 final long now = s.injectCurrentTimeMillis(); 414 if (ShortcutService.isClockValid(now) && mLastResetTime > now) { 415 Slog.w(TAG, "Clock rewound"); 416 // Clock rewound. 417 mLastResetTime = now; 418 mApiCallCount = 0; 419 return mApiCallCount; 420 } 421 422 // If not reset yet, then reset. 423 if (mLastResetTime < last) { 424 if (ShortcutService.DEBUG) { 425 Slog.d(TAG, String.format("%s: last reset=%d, now=%d, last=%d: resetting", 426 getPackageName(), mLastResetTime, now, last)); 427 } 428 mApiCallCount = 0; 429 mLastResetTime = last; 430 } 431 return mApiCallCount; 432 } 433 434 /** 435 * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount} 436 * and return true. Otherwise just return false. 437 * 438 * <p>This takes care of the resetting the counter for foreground apps as well as after 439 * locale changes, which is done internally by {@link #getApiCallCount}. 440 */ 441 public boolean tryApiCall() { 442 final ShortcutService s = mShortcutUser.mService; 443 444 if (getApiCallCount() >= s.mMaxUpdatesPerInterval) { 445 return false; 446 } 447 mApiCallCount++; 448 s.scheduleSaveUser(getOwnerUserId()); 449 return true; 450 } 451 452 public void resetRateLimiting() { 453 if (ShortcutService.DEBUG) { 454 Slog.d(TAG, "resetRateLimiting: " + getPackageName()); 455 } 456 if (mApiCallCount > 0) { 457 mApiCallCount = 0; 458 mShortcutUser.mService.scheduleSaveUser(getOwnerUserId()); 459 } 460 } 461 462 public void resetRateLimitingForCommandLineNoSaving() { 463 mApiCallCount = 0; 464 mLastResetTime = 0; 465 } 466 467 /** 468 * Find all shortcuts that match {@code query}. 469 */ 470 public void findAll(@NonNull List<ShortcutInfo> result, 471 @Nullable Predicate<ShortcutInfo> query, int cloneFlag) { 472 findAll(result, query, cloneFlag, null, 0); 473 } 474 475 /** 476 * Find all shortcuts that match {@code query}. 477 * 478 * This will also provide a "view" for each launcher -- a non-dynamic shortcut that's not pinned 479 * by the calling launcher will not be included in the result, and also "isPinned" will be 480 * adjusted for the caller too. 481 */ 482 public void findAll(@NonNull List<ShortcutInfo> result, 483 @Nullable Predicate<ShortcutInfo> query, int cloneFlag, 484 @Nullable String callingLauncher, int launcherUserId) { 485 if (getPackageInfo().isShadow()) { 486 // Restored and the app not installed yet, so don't return any. 487 return; 488 } 489 490 final ShortcutService s = mShortcutUser.mService; 491 492 // Set of pinned shortcuts by the calling launcher. 493 final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null 494 : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) 495 .getPinnedShortcutIds(getPackageName(), getPackageUserId()); 496 497 for (int i = 0; i < mShortcuts.size(); i++) { 498 final ShortcutInfo si = mShortcuts.valueAt(i); 499 500 // Need to adjust PINNED flag depending on the caller. 501 // Basically if the caller is a launcher (callingLauncher != null) and the launcher 502 // isn't pinning it, then we need to clear PINNED for this caller. 503 final boolean isPinnedByCaller = (callingLauncher == null) 504 || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId())); 505 506 if (si.isFloating()) { 507 if (!isPinnedByCaller) { 508 continue; 509 } 510 } 511 final ShortcutInfo clone = si.clone(cloneFlag); 512 513 // Fix up isPinned for the caller. Note we need to do it before the "test" callback, 514 // since it may check isPinned. 515 if (!isPinnedByCaller) { 516 clone.clearFlags(ShortcutInfo.FLAG_PINNED); 517 } 518 if (query == null || query.test(clone)) { 519 result.add(clone); 520 } 521 } 522 } 523 524 public void resetThrottling() { 525 mApiCallCount = 0; 526 } 527 528 /** 529 * Return the filenames (excluding path names) of icon bitmap files from this package. 530 */ 531 public ArraySet<String> getUsedBitmapFiles() { 532 final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size()); 533 534 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 535 final ShortcutInfo si = mShortcuts.valueAt(i); 536 if (si.getBitmapPath() != null) { 537 usedFiles.add(getFileName(si.getBitmapPath())); 538 } 539 } 540 return usedFiles; 541 } 542 543 private static String getFileName(@NonNull String path) { 544 final int sep = path.lastIndexOf(File.separatorChar); 545 if (sep == -1) { 546 return path; 547 } else { 548 return path.substring(sep + 1); 549 } 550 } 551 552 /** 553 * Called when the package is updated or added. 554 * 555 * Add case: 556 * - Publish manifest shortcuts. 557 * 558 * Update case: 559 * - Re-publish manifest shortcuts. 560 * - If there are shortcuts with resources (icons or strings), update their timestamps. 561 * 562 * @return TRUE if any shortcuts have been changed. 563 */ 564 public boolean handlePackageAddedOrUpdated(boolean isNewApp) { 565 final PackageInfo pi = mShortcutUser.mService.getPackageInfo( 566 getPackageName(), getPackageUserId()); 567 if (pi == null) { 568 return false; // Shouldn't happen. 569 } 570 571 if (!isNewApp) { 572 // Make sure the version code or last update time has changed. 573 // Otherwise, nothing to do. 574 if (getPackageInfo().getVersionCode() >= pi.versionCode 575 && getPackageInfo().getLastUpdateTime() >= pi.lastUpdateTime) { 576 return false; 577 } 578 } 579 580 // Now prepare to publish manifest shortcuts. 581 List<ShortcutInfo> newManifestShortcutList = null; 582 try { 583 newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService, 584 getPackageName(), getPackageUserId()); 585 } catch (IOException|XmlPullParserException e) { 586 Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e); 587 } 588 final int manifestShortcutSize = newManifestShortcutList == null ? 0 589 : newManifestShortcutList.size(); 590 if (ShortcutService.DEBUG) { 591 Slog.d(TAG, String.format("Package %s has %d manifest shortcut(s)", 592 getPackageName(), manifestShortcutSize)); 593 } 594 if (isNewApp && (manifestShortcutSize == 0)) { 595 // If it's a new app, and it doesn't have manifest shortcuts, then nothing to do. 596 597 // If it's an update, then it may already have manifest shortcuts, which need to be 598 // disabled. 599 return false; 600 } 601 if (ShortcutService.DEBUG) { 602 Slog.d(TAG, String.format("Package %s %s, version %d -> %d", getPackageName(), 603 (isNewApp ? "added" : "updated"), 604 getPackageInfo().getVersionCode(), pi.versionCode)); 605 } 606 607 getPackageInfo().updateVersionInfo(pi); 608 609 final ShortcutService s = mShortcutUser.mService; 610 611 boolean changed = false; 612 613 // For existing shortcuts, update timestamps if they have any resources. 614 if (!isNewApp) { 615 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 616 final ShortcutInfo si = mShortcuts.valueAt(i); 617 618 if (si.hasAnyResources()) { 619 changed = true; 620 si.setTimestamp(s.injectCurrentTimeMillis()); 621 } 622 } 623 } 624 625 // (Re-)publish manifest shortcut. 626 changed |= publishManifestShortcuts(newManifestShortcutList); 627 628 if (changed) { 629 // This will send a notification to the launcher, and also save . 630 s.packageShortcutsChanged(getPackageName(), getPackageUserId()); 631 } else { 632 // Still save the version code. 633 s.scheduleSaveUser(getPackageUserId()); 634 } 635 return changed; 636 } 637 638 private boolean publishManifestShortcuts(List<ShortcutInfo> newManifestShortcutList) { 639 if (ShortcutService.DEBUG) { 640 Slog.d(TAG, String.format( 641 "Package %s: publishing manifest shortcuts", getPackageName())); 642 } 643 boolean changed = false; 644 645 // TODO: Check dynamic count 646 647 // TODO: Kick out dynamic if too many 648 649 // Keep the previous IDs. 650 ArraySet<String> toDisableList = null; 651 for (int i = mShortcuts.size() - 1; i >= 0; i--) { 652 final ShortcutInfo si = mShortcuts.valueAt(i); 653 654 if (si.isManifestShortcut()) { 655 if (toDisableList == null) { 656 toDisableList = new ArraySet<>(); 657 } 658 toDisableList.add(si.getId()); 659 } 660 } 661 662 // Publish new ones. 663 if (newManifestShortcutList != null) { 664 final int newListSize = newManifestShortcutList.size(); 665 666 for (int i = 0; i < newListSize; i++) { 667 changed = true; 668 669 final ShortcutInfo newShortcut = newManifestShortcutList.get(i); 670 final boolean newDisabled = !newShortcut.isEnabled(); 671 672 final String id = newShortcut.getId(); 673 final ShortcutInfo oldShortcut = mShortcuts.get(id); 674 675 boolean wasPinned = false; 676 677 if (oldShortcut != null) { 678 if (!oldShortcut.isOriginallyFromManifest()) { 679 Slog.e(TAG, "Shortcut with ID=" + newShortcut.getId() 680 + " exists but is not from AndroidManifest.xml, not updating."); 681 continue; 682 } 683 // Take over the pinned flag. 684 if (oldShortcut.isPinned()) { 685 wasPinned = true; 686 newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); 687 } 688 } 689 if (newDisabled && !wasPinned) { 690 // If the shortcut is disabled, and it was *not* pinned, then this 691 // just doesn't have to be published. 692 // Just keep it in toDisableList, so the previous one would be removed. 693 continue; 694 } 695 // TODO: Check dynamic count 696 697 // Note even if enabled=false, we still need to update all fields, so do it 698 // regardless. 699 addShortcutInner(newShortcut); // This will clean up the old one too. 700 701 if (!newDisabled && toDisableList != null) { 702 // Still alive, don't remove. 703 toDisableList.remove(id); 704 } 705 } 706 } 707 708 // Disable the previous manifest shortcuts that are no longer in the manifest. 709 if (toDisableList != null) { 710 if (ShortcutService.DEBUG) { 711 Slog.d(TAG, String.format( 712 "Package %s: disabling %d stale shortcuts", getPackageName(), 713 toDisableList.size())); 714 } 715 for (int i = toDisableList.size() - 1; i >= 0; i--) { 716 changed = true; 717 718 final String id = toDisableList.valueAt(i); 719 720 disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0, 721 /* overrideImmutable=*/ true); 722 } 723 removeOrphans(); 724 } 725 return changed; 726 } 727 728 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 729 pw.println(); 730 731 pw.print(prefix); 732 pw.print("Package: "); 733 pw.print(getPackageName()); 734 pw.print(" UID: "); 735 pw.print(mPackageUid); 736 pw.println(); 737 738 pw.print(prefix); 739 pw.print(" "); 740 pw.print("Calls: "); 741 pw.print(getApiCallCount()); 742 pw.println(); 743 744 // getApiCallCount() may have updated mLastKnownForegroundElapsedTime. 745 pw.print(prefix); 746 pw.print(" "); 747 pw.print("Last known FG: "); 748 pw.print(mLastKnownForegroundElapsedTime); 749 pw.println(); 750 751 // This should be after getApiCallCount(), which may update it. 752 pw.print(prefix); 753 pw.print(" "); 754 pw.print("Last reset: ["); 755 pw.print(mLastResetTime); 756 pw.print("] "); 757 pw.print(ShortcutService.formatTime(mLastResetTime)); 758 pw.println(); 759 760 getPackageInfo().dump(pw, prefix + " "); 761 pw.println(); 762 763 pw.print(prefix); 764 pw.println(" Shortcuts:"); 765 long totalBitmapSize = 0; 766 final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts; 767 final int size = shortcuts.size(); 768 for (int i = 0; i < size; i++) { 769 final ShortcutInfo si = shortcuts.valueAt(i); 770 pw.print(prefix); 771 pw.print(" "); 772 pw.println(si.toInsecureString()); 773 if (si.getBitmapPath() != null) { 774 final long len = new File(si.getBitmapPath()).length(); 775 pw.print(prefix); 776 pw.print(" "); 777 pw.print("bitmap size="); 778 pw.println(len); 779 780 totalBitmapSize += len; 781 } 782 } 783 pw.print(prefix); 784 pw.print(" "); 785 pw.print("Total bitmap size: "); 786 pw.print(totalBitmapSize); 787 pw.print(" ("); 788 pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize)); 789 pw.println(")"); 790 } 791 792 @Override 793 public void saveToXml(@NonNull XmlSerializer out, boolean forBackup) 794 throws IOException, XmlPullParserException { 795 final int size = mShortcuts.size(); 796 797 if (size == 0 && mApiCallCount == 0) { 798 return; // nothing to write. 799 } 800 801 out.startTag(null, TAG_ROOT); 802 803 ShortcutService.writeAttr(out, ATTR_NAME, getPackageName()); 804 ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); 805 ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); 806 getPackageInfo().saveToXml(out); 807 808 for (int j = 0; j < size; j++) { 809 saveShortcut(out, mShortcuts.valueAt(j), forBackup); 810 } 811 812 out.endTag(null, TAG_ROOT); 813 } 814 815 private static void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup) 816 throws IOException, XmlPullParserException { 817 if (forBackup) { 818 if (!si.isPinned()) { 819 return; // Backup only pinned icons. 820 } 821 } 822 out.startTag(null, TAG_SHORTCUT); 823 ShortcutService.writeAttr(out, ATTR_ID, si.getId()); 824 // writeAttr(out, "package", si.getPackageName()); // not needed 825 ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivity()); 826 // writeAttr(out, "icon", si.getIcon()); // We don't save it. 827 ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); 828 ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId()); 829 ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); 830 ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId()); 831 ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage()); 832 ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID, 833 si.getDisabledMessageResourceId()); 834 ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras()); 835 ShortcutService.writeAttr(out, ATTR_RANK, si.getRank()); 836 ShortcutService.writeAttr(out, ATTR_TIMESTAMP, 837 si.getLastChangedTimestamp()); 838 if (forBackup) { 839 // Don't write icon information. Also drop the dynamic flag. 840 ShortcutService.writeAttr(out, ATTR_FLAGS, 841 si.getFlags() & 842 ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES 843 | ShortcutInfo.FLAG_DYNAMIC)); 844 } else { 845 ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags()); 846 ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId()); 847 ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); 848 } 849 850 { 851 final Set<String> cat = si.getCategories(); 852 if (cat != null && cat.size() > 0) { 853 out.startTag(null, TAG_CATEGORIES); 854 XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]), 855 NAME_CATEGORIES, out); 856 out.endTag(null, TAG_CATEGORIES); 857 } 858 } 859 860 ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS, 861 si.getIntentPersistableExtras()); 862 ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); 863 864 out.endTag(null, TAG_SHORTCUT); 865 } 866 867 public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser, 868 XmlPullParser parser, boolean fromBackup) 869 throws IOException, XmlPullParserException { 870 871 final String packageName = ShortcutService.parseStringAttribute(parser, 872 ATTR_NAME); 873 874 final ShortcutPackage ret = new ShortcutPackage(shortcutUser, 875 shortcutUser.getUserId(), packageName); 876 877 ret.mApiCallCount = 878 ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); 879 ret.mLastResetTime = 880 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); 881 882 final int outerDepth = parser.getDepth(); 883 int type; 884 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 885 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 886 if (type != XmlPullParser.START_TAG) { 887 continue; 888 } 889 final int depth = parser.getDepth(); 890 final String tag = parser.getName(); 891 if (depth == outerDepth + 1) { 892 switch (tag) { 893 case ShortcutPackageInfo.TAG_ROOT: 894 ret.getPackageInfo().loadFromXml(parser, fromBackup); 895 continue; 896 case TAG_SHORTCUT: 897 final ShortcutInfo si = parseShortcut(parser, packageName, 898 shortcutUser.getUserId()); 899 900 // Don't use addShortcut(), we don't need to save the icon. 901 ret.mShortcuts.put(si.getId(), si); 902 continue; 903 } 904 } 905 ShortcutService.warnForInvalidTag(depth, tag); 906 } 907 return ret; 908 } 909 910 private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName, 911 @UserIdInt int userId) throws IOException, XmlPullParserException { 912 String id; 913 ComponentName activityComponent; 914 // Icon icon; 915 String title; 916 int titleResId; 917 String text; 918 int textResId; 919 String disabledMessage; 920 int disabledMessageResId; 921 Intent intent; 922 PersistableBundle intentPersistableExtras = null; 923 int rank; 924 PersistableBundle extras = null; 925 long lastChangedTimestamp; 926 int flags; 927 int iconRes; 928 String bitmapPath; 929 ArraySet<String> categories = null; 930 931 id = ShortcutService.parseStringAttribute(parser, ATTR_ID); 932 activityComponent = ShortcutService.parseComponentNameAttribute(parser, 933 ATTR_ACTIVITY); 934 title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); 935 titleResId = ShortcutService.parseIntAttribute(parser, ATTR_TITLE_RES_ID); 936 text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT); 937 textResId = ShortcutService.parseIntAttribute(parser, ATTR_TEXT_RES_ID); 938 disabledMessage = ShortcutService.parseStringAttribute(parser, ATTR_DISABLED_MESSAGE); 939 disabledMessageResId = ShortcutService.parseIntAttribute(parser, 940 ATTR_DISABLED_MESSAGE_RES_ID); 941 intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT); 942 rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK); 943 lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP); 944 flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS); 945 iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES); 946 bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH); 947 948 final int outerDepth = parser.getDepth(); 949 int type; 950 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 951 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 952 if (type != XmlPullParser.START_TAG) { 953 continue; 954 } 955 final int depth = parser.getDepth(); 956 final String tag = parser.getName(); 957 if (ShortcutService.DEBUG_LOAD) { 958 Slog.d(TAG, String.format(" depth=%d type=%d name=%s", 959 depth, type, tag)); 960 } 961 switch (tag) { 962 case TAG_INTENT_EXTRAS: 963 intentPersistableExtras = PersistableBundle.restoreFromXml(parser); 964 continue; 965 case TAG_EXTRAS: 966 extras = PersistableBundle.restoreFromXml(parser); 967 continue; 968 case TAG_CATEGORIES: 969 // This just contains string-array. 970 continue; 971 case TAG_STRING_ARRAY_XMLUTILS: 972 if (NAME_CATEGORIES.equals(ShortcutService.parseStringAttribute(parser, 973 ATTR_NAME_XMLUTILS))) { 974 final String[] ar = XmlUtils.readThisStringArrayXml( 975 parser, TAG_STRING_ARRAY_XMLUTILS, null); 976 categories = new ArraySet<>(ar.length); 977 for (int i = 0; i < ar.length; i++) { 978 categories.add(ar[i]); 979 } 980 } 981 continue; 982 } 983 throw ShortcutService.throwForInvalidTag(depth, tag); 984 } 985 986 return new ShortcutInfo( 987 userId, id, packageName, activityComponent, /* icon =*/ null, 988 title, titleResId, text, textResId, disabledMessage, disabledMessageResId, 989 categories, intent, 990 intentPersistableExtras, rank, extras, lastChangedTimestamp, flags, 991 iconRes, bitmapPath); 992 } 993 994 @VisibleForTesting 995 List<ShortcutInfo> getAllShortcutsForTest() { 996 return new ArrayList<>(mShortcuts.values()); 997 } 998} 999