ShortcutUser.java revision c160fa4cc4f0ae85b3dd4169a0cd1165c183b0cf
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.pm.ShortcutManager; 23import android.text.TextUtils; 24import android.text.format.Formatter; 25import android.util.ArrayMap; 26import android.util.ArraySet; 27import android.util.Log; 28import android.util.Slog; 29 30import com.android.internal.annotations.VisibleForTesting; 31import com.android.internal.util.Preconditions; 32import com.android.server.pm.ShortcutService.DumpFilter; 33import com.android.server.pm.ShortcutService.InvalidFileFormatException; 34 35import libcore.util.Objects; 36 37import org.json.JSONArray; 38import org.json.JSONException; 39import org.json.JSONObject; 40import org.xmlpull.v1.XmlPullParser; 41import org.xmlpull.v1.XmlPullParserException; 42import org.xmlpull.v1.XmlSerializer; 43 44import java.io.File; 45import java.io.IOException; 46import java.io.PrintWriter; 47import java.util.function.Consumer; 48 49/** 50 * User information used by {@link ShortcutService}. 51 * 52 * All methods should be guarded by {@code #mService.mLock}. 53 */ 54class ShortcutUser { 55 private static final String TAG = ShortcutService.TAG; 56 57 static final String TAG_ROOT = "user"; 58 private static final String TAG_LAUNCHER = "launcher"; 59 60 private static final String ATTR_VALUE = "value"; 61 private static final String ATTR_KNOWN_LOCALES = "locales"; 62 63 // Suffix "2" was added to force rescan all packages after the next OTA. 64 private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time2"; 65 private static final String ATTR_LAST_APP_SCAN_OS_FINGERPRINT = "last-app-scan-fp"; 66 private static final String KEY_USER_ID = "userId"; 67 private static final String KEY_LAUNCHERS = "launchers"; 68 private static final String KEY_PACKAGES = "packages"; 69 70 static final class PackageWithUser { 71 final int userId; 72 final String packageName; 73 74 private PackageWithUser(int userId, String packageName) { 75 this.userId = userId; 76 this.packageName = Preconditions.checkNotNull(packageName); 77 } 78 79 public static PackageWithUser of(int userId, String packageName) { 80 return new PackageWithUser(userId, packageName); 81 } 82 83 public static PackageWithUser of(ShortcutPackageItem spi) { 84 return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName()); 85 } 86 87 @Override 88 public int hashCode() { 89 return packageName.hashCode() ^ userId; 90 } 91 92 @Override 93 public boolean equals(Object obj) { 94 if (!(obj instanceof PackageWithUser)) { 95 return false; 96 } 97 final PackageWithUser that = (PackageWithUser) obj; 98 99 return userId == that.userId && packageName.equals(that.packageName); 100 } 101 102 @Override 103 public String toString() { 104 return String.format("[Package: %d, %s]", userId, packageName); 105 } 106 } 107 108 final ShortcutService mService; 109 110 @UserIdInt 111 private final int mUserId; 112 113 private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); 114 115 private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); 116 117 /** 118 * Last known launcher. It's used when the default launcher isn't set in PM -- i.e. 119 * when getHomeActivitiesAsUser() return null. We need it so that in this situation the 120 * previously default launcher can still access shortcuts. 121 */ 122 private ComponentName mLastKnownLauncher; 123 124 /** In-memory-cached default launcher. */ 125 private ComponentName mCachedLauncher; 126 127 /** 128 * Keep track of additional packages that other parts of the system have said are 129 * allowed to access shortcuts. The key is the part of the system it came from, 130 * the value is the package name that has access. We don't persist these because 131 * at boot all relevant system services will push this data back to us they do their 132 * normal evaluation of the state of the world. 133 */ 134 private final ArrayMap<String, String> mHostPackages = new ArrayMap<>(); 135 136 /** 137 * Set of package name values from above. 138 */ 139 private final ArraySet<String> mHostPackageSet = new ArraySet<>(); 140 141 private String mKnownLocales; 142 143 private long mLastAppScanTime; 144 145 private String mLastAppScanOsFingerprint; 146 147 public ShortcutUser(ShortcutService service, int userId) { 148 mService = service; 149 mUserId = userId; 150 } 151 152 public int getUserId() { 153 return mUserId; 154 } 155 156 public long getLastAppScanTime() { 157 return mLastAppScanTime; 158 } 159 160 public void setLastAppScanTime(long lastAppScanTime) { 161 mLastAppScanTime = lastAppScanTime; 162 } 163 164 public String getLastAppScanOsFingerprint() { 165 return mLastAppScanOsFingerprint; 166 } 167 168 public void setLastAppScanOsFingerprint(String lastAppScanOsFingerprint) { 169 mLastAppScanOsFingerprint = lastAppScanOsFingerprint; 170 } 171 172 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 173 // remove from it. 174 @VisibleForTesting 175 ArrayMap<String, ShortcutPackage> getAllPackagesForTest() { 176 return mPackages; 177 } 178 179 public boolean hasPackage(@NonNull String packageName) { 180 return mPackages.containsKey(packageName); 181 } 182 183 private void addPackage(@NonNull ShortcutPackage p) { 184 p.replaceUser(this); 185 mPackages.put(p.getPackageName(), p); 186 } 187 188 public ShortcutPackage removePackage(@NonNull String packageName) { 189 final ShortcutPackage removed = mPackages.remove(packageName); 190 191 mService.cleanupBitmapsForPackage(mUserId, packageName); 192 193 return removed; 194 } 195 196 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 197 // remove from it. 198 @VisibleForTesting 199 ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() { 200 return mLaunchers; 201 } 202 203 private void addLauncher(ShortcutLauncher launcher) { 204 launcher.replaceUser(this); 205 mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), 206 launcher.getPackageName()), launcher); 207 } 208 209 @Nullable 210 public ShortcutLauncher removeLauncher( 211 @UserIdInt int packageUserId, @NonNull String packageName) { 212 return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); 213 } 214 215 @Nullable 216 public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) { 217 final ShortcutPackage ret = mPackages.get(packageName); 218 if (ret != null) { 219 ret.attemptToRestoreIfNeededAndSave(); 220 } 221 return ret; 222 } 223 224 @NonNull 225 public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { 226 ShortcutPackage ret = getPackageShortcutsIfExists(packageName); 227 if (ret == null) { 228 ret = new ShortcutPackage(this, mUserId, packageName); 229 mPackages.put(packageName, ret); 230 } 231 return ret; 232 } 233 234 @NonNull 235 public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName, 236 @UserIdInt int launcherUserId) { 237 final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); 238 ShortcutLauncher ret = mLaunchers.get(key); 239 if (ret == null) { 240 ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId); 241 mLaunchers.put(key, ret); 242 } else { 243 ret.attemptToRestoreIfNeededAndSave(); 244 } 245 return ret; 246 } 247 248 public void forAllPackages(Consumer<? super ShortcutPackage> callback) { 249 final int size = mPackages.size(); 250 for (int i = 0; i < size; i++) { 251 callback.accept(mPackages.valueAt(i)); 252 } 253 } 254 255 public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) { 256 final int size = mLaunchers.size(); 257 for (int i = 0; i < size; i++) { 258 callback.accept(mLaunchers.valueAt(i)); 259 } 260 } 261 262 public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) { 263 forAllLaunchers(callback); 264 forAllPackages(callback); 265 } 266 267 public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, 268 Consumer<ShortcutPackageItem> callback) { 269 forAllPackageItems(spi -> { 270 if ((spi.getPackageUserId() == packageUserId) 271 && spi.getPackageName().equals(packageName)) { 272 callback.accept(spi); 273 } 274 }); 275 } 276 277 /** 278 * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the 279 * information on the package is up-to-date. 280 * 281 * We use broadcasts to handle locale changes and package changes, but because broadcasts 282 * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event 283 * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast. 284 * 285 * So we call this method at all entry points from publishers to make sure we update all 286 * relevant information. 287 * 288 * Similar inconsistencies can happen when the launcher fetches shortcut information, but 289 * that's a less of an issue because for the launcher we report shortcut changes with 290 * callbacks. 291 */ 292 public void onCalledByPublisher(@NonNull String packageName) { 293 detectLocaleChange(); 294 rescanPackageIfNeeded(packageName, /*forceRescan=*/ false); 295 } 296 297 private String getKnownLocales() { 298 if (TextUtils.isEmpty(mKnownLocales)) { 299 mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId); 300 mService.scheduleSaveUser(mUserId); 301 } 302 return mKnownLocales; 303 } 304 305 /** 306 * Check to see if the system locale has changed, and if so, reset throttling 307 * and update resource strings. 308 */ 309 public void detectLocaleChange() { 310 final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId); 311 if (getKnownLocales().equals(currentLocales)) { 312 return; 313 } 314 if (ShortcutService.DEBUG) { 315 Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales 316 + " for user " + mUserId); 317 } 318 mKnownLocales = currentLocales; 319 320 forAllPackages(pkg -> { 321 pkg.resetRateLimiting(); 322 pkg.resolveResourceStrings(); 323 }); 324 325 mService.scheduleSaveUser(mUserId); 326 } 327 328 public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) { 329 final boolean isNewApp = !mPackages.containsKey(packageName); 330 331 final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName); 332 333 if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) { 334 if (isNewApp) { 335 mPackages.remove(packageName); 336 } 337 } 338 } 339 340 public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, 341 @UserIdInt int packageUserId) { 342 forPackageItem(packageName, packageUserId, spi -> { 343 spi.attemptToRestoreIfNeededAndSave(); 344 }); 345 } 346 347 public void saveToXml(XmlSerializer out, boolean forBackup) 348 throws IOException, XmlPullParserException { 349 out.startTag(null, TAG_ROOT); 350 351 if (!forBackup) { 352 // Don't have to back them up. 353 ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales); 354 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, 355 mLastAppScanTime); 356 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT, 357 mLastAppScanOsFingerprint); 358 359 ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher); 360 } 361 362 // Can't use forEachPackageItem due to the checked exceptions. 363 { 364 final int size = mLaunchers.size(); 365 for (int i = 0; i < size; i++) { 366 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup); 367 } 368 } 369 { 370 final int size = mPackages.size(); 371 for (int i = 0; i < size; i++) { 372 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup); 373 } 374 } 375 376 out.endTag(null, TAG_ROOT); 377 } 378 379 private void saveShortcutPackageItem(XmlSerializer out, 380 ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { 381 if (forBackup) { 382 if (spi.getPackageUserId() != spi.getOwnerUserId()) { 383 return; // Don't save cross-user information. 384 } 385 } 386 spi.saveToXml(out, forBackup); 387 } 388 389 public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, 390 boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException { 391 final ShortcutUser ret = new ShortcutUser(s, userId); 392 393 try { 394 ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, 395 ATTR_KNOWN_LOCALES); 396 397 // If lastAppScanTime is in the future, that means the clock went backwards. 398 // Just scan all apps again. 399 final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, 400 ATTR_LAST_APP_SCAN_TIME); 401 final long currentTime = s.injectCurrentTimeMillis(); 402 ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; 403 ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser, 404 ATTR_LAST_APP_SCAN_OS_FINGERPRINT); 405 final int outerDepth = parser.getDepth(); 406 int type; 407 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 408 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 409 if (type != XmlPullParser.START_TAG) { 410 continue; 411 } 412 final int depth = parser.getDepth(); 413 final String tag = parser.getName(); 414 415 if (depth == outerDepth + 1) { 416 switch (tag) { 417 case TAG_LAUNCHER: { 418 ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute( 419 parser, ATTR_VALUE); 420 continue; 421 } 422 case ShortcutPackage.TAG_ROOT: { 423 final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( 424 s, ret, parser, fromBackup); 425 426 // Don't use addShortcut(), we don't need to save the icon. 427 ret.mPackages.put(shortcuts.getPackageName(), shortcuts); 428 continue; 429 } 430 431 case ShortcutLauncher.TAG_ROOT: { 432 ret.addLauncher( 433 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); 434 continue; 435 } 436 } 437 } 438 ShortcutService.warnForInvalidTag(depth, tag); 439 } 440 } catch (RuntimeException e) { 441 throw new ShortcutService.InvalidFileFormatException( 442 "Unable to parse file", e); 443 } 444 return ret; 445 } 446 447 public ComponentName getLastKnownLauncher() { 448 return mLastKnownLauncher; 449 } 450 451 public void setLauncher(ComponentName launcherComponent) { 452 setLauncher(launcherComponent, /* allowPurgeLastKnown */ false); 453 } 454 455 /** Clears the launcher information without clearing the last known one */ 456 public void clearLauncher() { 457 setLauncher(null); 458 } 459 460 /** 461 * Clears the launcher information *with(* clearing the last known one; we do this witl 462 * "cmd shortcut clear-default-launcher". 463 */ 464 public void forceClearLauncher() { 465 setLauncher(null, /* allowPurgeLastKnown */ true); 466 } 467 468 private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) { 469 mCachedLauncher = launcherComponent; // Always update the in-memory cache. 470 471 if (Objects.equal(mLastKnownLauncher, launcherComponent)) { 472 return; 473 } 474 if (!allowPurgeLastKnown && launcherComponent == null) { 475 return; 476 } 477 mLastKnownLauncher = launcherComponent; 478 mService.scheduleSaveUser(mUserId); 479 } 480 481 public ComponentName getCachedLauncher() { 482 return mCachedLauncher; 483 } 484 485 public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName) { 486 if (packageName != null) { 487 mHostPackages.put(type, packageName); 488 } else { 489 mHostPackages.remove(type); 490 } 491 492 mHostPackageSet.clear(); 493 for (int i = 0; i < mHostPackages.size(); i++) { 494 mHostPackageSet.add(mHostPackages.valueAt(i)); 495 } 496 } 497 498 public boolean hasHostPackage(@NonNull String packageName) { 499 return mHostPackageSet.contains(packageName); 500 } 501 502 public void resetThrottling() { 503 for (int i = mPackages.size() - 1; i >= 0; i--) { 504 mPackages.valueAt(i).resetThrottling(); 505 } 506 } 507 508 public void mergeRestoredFile(ShortcutUser restored) { 509 final ShortcutService s = mService; 510 // Note, a restore happens only at the end of setup wizard. At this point, no apps are 511 // installed from Play Store yet, but it's still possible that system apps have already 512 // published dynamic shortcuts, since some apps do so on BOOT_COMPLETED. 513 // When such a system app has allowbackup=true, then we go ahead and replace all existing 514 // shortcuts with the restored shortcuts. (Then we'll re-publish manifest shortcuts later 515 // in the call site.) 516 // When such a system app has allowbackup=false, then we'll keep the shortcuts that have 517 // already been published. So we selectively add restored ShortcutPackages here. 518 // 519 // The same logic applies to launchers, but since launchers shouldn't pin shortcuts 520 // without users interaction it's really not a big deal, so we just clear existing 521 // ShortcutLauncher instances in mLaunchers and add all the restored ones here. 522 523 int[] restoredLaunchers = new int[1]; 524 int[] restoredPackages = new int[1]; 525 int[] restoredShortcuts = new int[1]; 526 527 mLaunchers.clear(); 528 restored.forAllLaunchers(sl -> { 529 // If the app is already installed and allowbackup = false, then ignore the restored 530 // data. 531 if (s.isPackageInstalled(sl.getPackageName(), getUserId()) 532 && !s.shouldBackupApp(sl.getPackageName(), getUserId())) { 533 return; 534 } 535 addLauncher(sl); 536 restoredLaunchers[0]++; 537 }); 538 restored.forAllPackages(sp -> { 539 // If the app is already installed and allowbackup = false, then ignore the restored 540 // data. 541 if (s.isPackageInstalled(sp.getPackageName(), getUserId()) 542 && !s.shouldBackupApp(sp.getPackageName(), getUserId())) { 543 return; 544 } 545 546 final ShortcutPackage previous = getPackageShortcutsIfExists(sp.getPackageName()); 547 if (previous != null && previous.hasNonManifestShortcuts()) { 548 Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored." 549 + " Existing non-manifeset shortcuts will be overwritten."); 550 } 551 addPackage(sp); 552 restoredPackages[0]++; 553 restoredShortcuts[0] += sp.getShortcutCount(); 554 }); 555 // Empty the launchers and packages in restored to avoid accidentally using them. 556 restored.mLaunchers.clear(); 557 restored.mPackages.clear(); 558 559 Slog.i(TAG, "Restored: L=" + restoredLaunchers[0] 560 + " P=" + restoredPackages[0] 561 + " S=" + restoredShortcuts[0]); 562 } 563 564 public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) { 565 if (filter.shouldDumpDetails()) { 566 pw.print(prefix); 567 pw.print("User: "); 568 pw.print(mUserId); 569 pw.print(" Known locales: "); 570 pw.print(mKnownLocales); 571 pw.print(" Last app scan: ["); 572 pw.print(mLastAppScanTime); 573 pw.print("] "); 574 pw.print(ShortcutService.formatTime(mLastAppScanTime)); 575 pw.print(" Last app scan FP: "); 576 pw.print(mLastAppScanOsFingerprint); 577 pw.println(); 578 579 prefix += prefix + " "; 580 581 pw.print(prefix); 582 pw.print("Cached launcher: "); 583 pw.print(mCachedLauncher); 584 pw.println(); 585 586 pw.print(prefix); 587 pw.print("Last known launcher: "); 588 pw.print(mLastKnownLauncher); 589 pw.println(); 590 591 if (mHostPackages.size() > 0) { 592 pw.print(prefix); 593 pw.println("Host packages:"); 594 for (int i = 0; i < mHostPackages.size(); i++) { 595 pw.print(prefix); 596 pw.print(" "); 597 pw.print(mHostPackages.keyAt(i)); 598 pw.print(": "); 599 pw.println(mHostPackages.valueAt(i)); 600 } 601 } 602 } 603 604 for (int i = 0; i < mLaunchers.size(); i++) { 605 ShortcutLauncher launcher = mLaunchers.valueAt(i); 606 if (filter.isPackageMatch(launcher.getPackageName())) { 607 launcher.dump(pw, prefix, filter); 608 } 609 } 610 611 for (int i = 0; i < mPackages.size(); i++) { 612 ShortcutPackage pkg = mPackages.valueAt(i); 613 if (filter.isPackageMatch(pkg.getPackageName())) { 614 pkg.dump(pw, prefix, filter); 615 } 616 } 617 618 if (filter.shouldDumpDetails()) { 619 pw.println(); 620 pw.print(prefix); 621 pw.println("Bitmap directories: "); 622 dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId)); 623 } 624 } 625 626 private void dumpDirectorySize(@NonNull PrintWriter pw, 627 @NonNull String prefix, File path) { 628 int numFiles = 0; 629 long size = 0; 630 final File[] children = path.listFiles(); 631 if (children != null) { 632 for (File child : path.listFiles()) { 633 if (child.isFile()) { 634 numFiles++; 635 size += child.length(); 636 } else if (child.isDirectory()) { 637 dumpDirectorySize(pw, prefix + " ", child); 638 } 639 } 640 } 641 pw.print(prefix); 642 pw.print("Path: "); 643 pw.print(path.getName()); 644 pw.print("/ has "); 645 pw.print(numFiles); 646 pw.print(" files, size="); 647 pw.print(size); 648 pw.print(" ("); 649 pw.print(Formatter.formatFileSize(mService.mContext, size)); 650 pw.println(")"); 651 } 652 653 public JSONObject dumpCheckin(boolean clear) throws JSONException { 654 final JSONObject result = new JSONObject(); 655 656 result.put(KEY_USER_ID, mUserId); 657 658 { 659 final JSONArray launchers = new JSONArray(); 660 for (int i = 0; i < mLaunchers.size(); i++) { 661 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear)); 662 } 663 result.put(KEY_LAUNCHERS, launchers); 664 } 665 666 { 667 final JSONArray packages = new JSONArray(); 668 for (int i = 0; i < mPackages.size(); i++) { 669 packages.put(mPackages.valueAt(i).dumpCheckin(clear)); 670 } 671 result.put(KEY_PACKAGES, packages); 672 } 673 674 return result; 675 } 676} 677