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