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 import android.util.SparseArray; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.util.Preconditions; 33 import com.android.server.pm.ShortcutService.InvalidFileFormatException; 34 35 import libcore.util.Objects; 36 37 import org.json.JSONArray; 38 import org.json.JSONException; 39 import org.json.JSONObject; 40 import org.xmlpull.v1.XmlPullParser; 41 import org.xmlpull.v1.XmlPullParserException; 42 import org.xmlpull.v1.XmlSerializer; 43 44 import java.io.File; 45 import java.io.IOException; 46 import java.io.PrintWriter; 47 import 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 */ 54 class 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 (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) { 369 return; // Don't save. 370 } 371 if (spi.getPackageUserId() != spi.getOwnerUserId()) { 372 return; // Don't save cross-user information. 373 } 374 } 375 spi.saveToXml(out, forBackup); 376 } 377 378 public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, 379 boolean fromBackup) throws IOException, XmlPullParserException, InvalidFileFormatException { 380 final ShortcutUser ret = new ShortcutUser(s, userId); 381 382 try { 383 ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, 384 ATTR_KNOWN_LOCALES); 385 386 // If lastAppScanTime is in the future, that means the clock went backwards. 387 // Just scan all apps again. 388 final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, 389 ATTR_LAST_APP_SCAN_TIME); 390 final long currentTime = s.injectCurrentTimeMillis(); 391 ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; 392 ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser, 393 ATTR_LAST_APP_SCAN_OS_FINGERPRINT); 394 final int outerDepth = parser.getDepth(); 395 int type; 396 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 397 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 398 if (type != XmlPullParser.START_TAG) { 399 continue; 400 } 401 final int depth = parser.getDepth(); 402 final String tag = parser.getName(); 403 404 if (depth == outerDepth + 1) { 405 switch (tag) { 406 case TAG_LAUNCHER: { 407 ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute( 408 parser, ATTR_VALUE); 409 continue; 410 } 411 case ShortcutPackage.TAG_ROOT: { 412 final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( 413 s, ret, parser, fromBackup); 414 415 // Don't use addShortcut(), we don't need to save the icon. 416 ret.mPackages.put(shortcuts.getPackageName(), shortcuts); 417 continue; 418 } 419 420 case ShortcutLauncher.TAG_ROOT: { 421 ret.addLauncher( 422 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); 423 continue; 424 } 425 } 426 } 427 ShortcutService.warnForInvalidTag(depth, tag); 428 } 429 } catch (RuntimeException e) { 430 throw new ShortcutService.InvalidFileFormatException( 431 "Unable to parse file", e); 432 } 433 return ret; 434 } 435 436 public ComponentName getLastKnownLauncher() { 437 return mLastKnownLauncher; 438 } 439 440 public void setLauncher(ComponentName launcherComponent) { 441 setLauncher(launcherComponent, /* allowPurgeLastKnown */ false); 442 } 443 444 /** Clears the launcher information without clearing the last known one */ 445 public void clearLauncher() { 446 setLauncher(null); 447 } 448 449 /** 450 * Clears the launcher information *with(* clearing the last known one; we do this witl 451 * "cmd shortcut clear-default-launcher". 452 */ 453 public void forceClearLauncher() { 454 setLauncher(null, /* allowPurgeLastKnown */ true); 455 } 456 457 private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) { 458 mCachedLauncher = launcherComponent; // Always update the in-memory cache. 459 460 if (Objects.equal(mLastKnownLauncher, launcherComponent)) { 461 return; 462 } 463 if (!allowPurgeLastKnown && launcherComponent == null) { 464 return; 465 } 466 mLastKnownLauncher = launcherComponent; 467 mService.scheduleSaveUser(mUserId); 468 } 469 470 public ComponentName getCachedLauncher() { 471 return mCachedLauncher; 472 } 473 474 public void resetThrottling() { 475 for (int i = mPackages.size() - 1; i >= 0; i--) { 476 mPackages.valueAt(i).resetThrottling(); 477 } 478 } 479 480 public void mergeRestoredFile(ShortcutUser restored) { 481 final ShortcutService s = mService; 482 // Note, a restore happens only at the end of setup wizard. At this point, no apps are 483 // installed from Play Store yet, but it's still possible that system apps have already 484 // published dynamic shortcuts, since some apps do so on BOOT_COMPLETED. 485 // When such a system app has allowbackup=true, then we go ahead and replace all existing 486 // shortcuts with the restored shortcuts. (Then we'll re-publish manifest shortcuts later 487 // in the call site.) 488 // When such a system app has allowbackup=false, then we'll keep the shortcuts that have 489 // already been published. So we selectively add restored ShortcutPackages here. 490 // 491 // The same logic applies to launchers, but since launchers shouldn't pin shortcuts 492 // without users interaction it's really not a big deal, so we just clear existing 493 // ShortcutLauncher instances in mLaunchers and add all the restored ones here. 494 495 mLaunchers.clear(); 496 restored.forAllLaunchers(sl -> { 497 // If the app is already installed and allowbackup = false, then ignore the restored 498 // data. 499 if (s.isPackageInstalled(sl.getPackageName(), getUserId()) 500 && !s.shouldBackupApp(sl.getPackageName(), getUserId())) { 501 return; 502 } 503 addLauncher(sl); 504 }); 505 restored.forAllPackages(sp -> { 506 // If the app is already installed and allowbackup = false, then ignore the restored 507 // data. 508 if (s.isPackageInstalled(sp.getPackageName(), getUserId()) 509 && !s.shouldBackupApp(sp.getPackageName(), getUserId())) { 510 return; 511 } 512 513 final ShortcutPackage previous = getPackageShortcutsIfExists(sp.getPackageName()); 514 if (previous != null && previous.hasNonManifestShortcuts()) { 515 Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored." 516 + " Existing non-manifeset shortcuts will be overwritten."); 517 } 518 addPackage(sp); 519 }); 520 // Empty the launchers and packages in restored to avoid accidentally using them. 521 restored.mLaunchers.clear(); 522 restored.mPackages.clear(); 523 } 524 525 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 526 pw.print(prefix); 527 pw.print("User: "); 528 pw.print(mUserId); 529 pw.print(" Known locales: "); 530 pw.print(mKnownLocales); 531 pw.print(" Last app scan: ["); 532 pw.print(mLastAppScanTime); 533 pw.print("] "); 534 pw.print(ShortcutService.formatTime(mLastAppScanTime)); 535 pw.print(" Last app scan FP: "); 536 pw.print(mLastAppScanOsFingerprint); 537 pw.println(); 538 539 prefix += prefix + " "; 540 541 pw.print(prefix); 542 pw.print("Cached launcher: "); 543 pw.print(mCachedLauncher); 544 pw.println(); 545 546 pw.print(prefix); 547 pw.print("Last known launcher: "); 548 pw.print(mLastKnownLauncher); 549 pw.println(); 550 551 for (int i = 0; i < mLaunchers.size(); i++) { 552 mLaunchers.valueAt(i).dump(pw, prefix); 553 } 554 555 for (int i = 0; i < mPackages.size(); i++) { 556 mPackages.valueAt(i).dump(pw, prefix); 557 } 558 559 pw.println(); 560 pw.print(prefix); 561 pw.println("Bitmap directories: "); 562 dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId)); 563 } 564 565 private void dumpDirectorySize(@NonNull PrintWriter pw, 566 @NonNull String prefix, File path) { 567 int numFiles = 0; 568 long size = 0; 569 final File[] children = path.listFiles(); 570 if (children != null) { 571 for (File child : path.listFiles()) { 572 if (child.isFile()) { 573 numFiles++; 574 size += child.length(); 575 } else if (child.isDirectory()) { 576 dumpDirectorySize(pw, prefix + " ", child); 577 } 578 } 579 } 580 pw.print(prefix); 581 pw.print("Path: "); 582 pw.print(path.getName()); 583 pw.print("/ has "); 584 pw.print(numFiles); 585 pw.print(" files, size="); 586 pw.print(size); 587 pw.print(" ("); 588 pw.print(Formatter.formatFileSize(mService.mContext, size)); 589 pw.println(")"); 590 } 591 592 public JSONObject dumpCheckin(boolean clear) throws JSONException { 593 final JSONObject result = new JSONObject(); 594 595 result.put(KEY_USER_ID, mUserId); 596 597 { 598 final JSONArray launchers = new JSONArray(); 599 for (int i = 0; i < mLaunchers.size(); i++) { 600 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear)); 601 } 602 result.put(KEY_LAUNCHERS, launchers); 603 } 604 605 { 606 final JSONArray packages = new JSONArray(); 607 for (int i = 0; i < mPackages.size(); i++) { 608 packages.put(mPackages.valueAt(i).dumpCheckin(clear)); 609 } 610 result.put(KEY_PACKAGES, packages); 611 } 612 613 return result; 614 } 615 } 616