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