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