ShortcutUser.java revision 3366328245621fa6e8fd764a4d3c2f073774d096
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.Slog; 27import android.util.SparseArray; 28 29import com.android.internal.annotations.GuardedBy; 30import com.android.internal.annotations.VisibleForTesting; 31import com.android.internal.util.Preconditions; 32 33import libcore.util.Objects; 34 35import org.json.JSONArray; 36import org.json.JSONException; 37import org.json.JSONObject; 38import org.xmlpull.v1.XmlPullParser; 39import org.xmlpull.v1.XmlPullParserException; 40import org.xmlpull.v1.XmlSerializer; 41 42import java.io.File; 43import java.io.IOException; 44import java.io.PrintWriter; 45import 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 */ 52class 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 public ShortcutPackage removePackage(@NonNull String packageName) { 168 final ShortcutPackage removed = mPackages.remove(packageName); 169 170 mService.cleanupBitmapsForPackage(mUserId, packageName); 171 172 return removed; 173 } 174 175 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 176 // remove from it. 177 @VisibleForTesting 178 ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() { 179 return mLaunchers; 180 } 181 182 public void addLauncher(ShortcutLauncher launcher) { 183 mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), 184 launcher.getPackageName()), launcher); 185 } 186 187 @Nullable 188 public ShortcutLauncher removeLauncher( 189 @UserIdInt int packageUserId, @NonNull String packageName) { 190 return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); 191 } 192 193 @Nullable 194 public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) { 195 final ShortcutPackage ret = mPackages.get(packageName); 196 if (ret != null) { 197 ret.attemptToRestoreIfNeededAndSave(); 198 } 199 return ret; 200 } 201 202 @NonNull 203 public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { 204 ShortcutPackage ret = getPackageShortcutsIfExists(packageName); 205 if (ret == null) { 206 ret = new ShortcutPackage(this, mUserId, packageName); 207 mPackages.put(packageName, ret); 208 } 209 return ret; 210 } 211 212 @NonNull 213 public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName, 214 @UserIdInt int launcherUserId) { 215 final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); 216 ShortcutLauncher ret = mLaunchers.get(key); 217 if (ret == null) { 218 ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId); 219 mLaunchers.put(key, ret); 220 } else { 221 ret.attemptToRestoreIfNeededAndSave(); 222 } 223 return ret; 224 } 225 226 public void forAllPackages(Consumer<? super ShortcutPackage> callback) { 227 final int size = mPackages.size(); 228 for (int i = 0; i < size; i++) { 229 callback.accept(mPackages.valueAt(i)); 230 } 231 } 232 233 public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) { 234 final int size = mLaunchers.size(); 235 for (int i = 0; i < size; i++) { 236 callback.accept(mLaunchers.valueAt(i)); 237 } 238 } 239 240 public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) { 241 forAllLaunchers(callback); 242 forAllPackages(callback); 243 } 244 245 public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, 246 Consumer<ShortcutPackageItem> callback) { 247 forAllPackageItems(spi -> { 248 if ((spi.getPackageUserId() == packageUserId) 249 && spi.getPackageName().equals(packageName)) { 250 callback.accept(spi); 251 } 252 }); 253 } 254 255 /** 256 * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the 257 * information on the package is up-to-date. 258 * 259 * We use broadcasts to handle locale changes and package changes, but because broadcasts 260 * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event 261 * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast. 262 * 263 * So we call this method at all entry points from publishers to make sure we update all 264 * relevant information. 265 * 266 * Similar inconsistencies can happen when the launcher fetches shortcut information, but 267 * that's a less of an issue because for the launcher we report shortcut changes with 268 * callbacks. 269 */ 270 public void onCalledByPublisher(@NonNull String packageName) { 271 detectLocaleChange(); 272 rescanPackageIfNeeded(packageName, /*forceRescan=*/ false); 273 } 274 275 private String getKnownLocales() { 276 if (TextUtils.isEmpty(mKnownLocales)) { 277 mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId); 278 mService.scheduleSaveUser(mUserId); 279 } 280 return mKnownLocales; 281 } 282 283 /** 284 * Check to see if the system locale has changed, and if so, reset throttling 285 * and update resource strings. 286 */ 287 public void detectLocaleChange() { 288 final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId); 289 if (getKnownLocales().equals(currentLocales)) { 290 return; 291 } 292 if (ShortcutService.DEBUG) { 293 Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales 294 + " for user " + mUserId); 295 } 296 mKnownLocales = currentLocales; 297 298 forAllPackages(pkg -> { 299 pkg.resetRateLimiting(); 300 pkg.resolveResourceStrings(); 301 }); 302 303 mService.scheduleSaveUser(mUserId); 304 } 305 306 public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) { 307 final boolean isNewApp = !mPackages.containsKey(packageName); 308 309 final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName); 310 311 if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) { 312 if (isNewApp) { 313 mPackages.remove(packageName); 314 } 315 } 316 } 317 318 public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, 319 @UserIdInt int packageUserId) { 320 forPackageItem(packageName, packageUserId, spi -> { 321 spi.attemptToRestoreIfNeededAndSave(); 322 }); 323 } 324 325 public void saveToXml(XmlSerializer out, boolean forBackup) 326 throws IOException, XmlPullParserException { 327 out.startTag(null, TAG_ROOT); 328 329 ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales); 330 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, 331 mLastAppScanTime); 332 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_OS_FINGERPRINT, 333 mLastAppScanOsFingerprint); 334 335 ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher); 336 337 // Can't use forEachPackageItem due to the checked exceptions. 338 { 339 final int size = mLaunchers.size(); 340 for (int i = 0; i < size; i++) { 341 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup); 342 } 343 } 344 { 345 final int size = mPackages.size(); 346 for (int i = 0; i < size; i++) { 347 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup); 348 } 349 } 350 351 out.endTag(null, TAG_ROOT); 352 } 353 354 private void saveShortcutPackageItem(XmlSerializer out, 355 ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { 356 if (forBackup) { 357 if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) { 358 return; // Don't save. 359 } 360 if (spi.getPackageUserId() != spi.getOwnerUserId()) { 361 return; // Don't save cross-user information. 362 } 363 } 364 spi.saveToXml(out, forBackup); 365 } 366 367 public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, 368 boolean fromBackup) throws IOException, XmlPullParserException { 369 final ShortcutUser ret = new ShortcutUser(s, userId); 370 371 ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, 372 ATTR_KNOWN_LOCALES); 373 374 // If lastAppScanTime is in the future, that means the clock went backwards. 375 // Just scan all apps again. 376 final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, 377 ATTR_LAST_APP_SCAN_TIME); 378 final long currentTime = s.injectCurrentTimeMillis(); 379 ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; 380 ret.mLastAppScanOsFingerprint = ShortcutService.parseStringAttribute(parser, 381 ATTR_LAST_APP_SCAN_OS_FINGERPRINT); 382 final int outerDepth = parser.getDepth(); 383 int type; 384 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 385 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 386 if (type != XmlPullParser.START_TAG) { 387 continue; 388 } 389 final int depth = parser.getDepth(); 390 final String tag = parser.getName(); 391 392 if (depth == outerDepth + 1) { 393 switch (tag) { 394 case TAG_LAUNCHER: { 395 ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute( 396 parser, ATTR_VALUE); 397 continue; 398 } 399 case ShortcutPackage.TAG_ROOT: { 400 final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( 401 s, ret, parser, fromBackup); 402 403 // Don't use addShortcut(), we don't need to save the icon. 404 ret.mPackages.put(shortcuts.getPackageName(), shortcuts); 405 continue; 406 } 407 408 case ShortcutLauncher.TAG_ROOT: { 409 ret.addLauncher( 410 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); 411 continue; 412 } 413 } 414 } 415 ShortcutService.warnForInvalidTag(depth, tag); 416 } 417 return ret; 418 } 419 420 public ComponentName getLastKnownLauncher() { 421 return mLastKnownLauncher; 422 } 423 424 public void setLauncher(ComponentName launcherComponent) { 425 setLauncher(launcherComponent, /* allowPurgeLastKnown */ false); 426 } 427 428 /** Clears the launcher information without clearing the last known one */ 429 public void clearLauncher() { 430 setLauncher(null); 431 } 432 433 /** 434 * Clears the launcher information *with(* clearing the last known one; we do this witl 435 * "cmd shortcut clear-default-launcher". 436 */ 437 public void forceClearLauncher() { 438 setLauncher(null, /* allowPurgeLastKnown */ true); 439 } 440 441 private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) { 442 mCachedLauncher = launcherComponent; // Always update the in-memory cache. 443 444 if (Objects.equal(mLastKnownLauncher, launcherComponent)) { 445 return; 446 } 447 if (!allowPurgeLastKnown && launcherComponent == null) { 448 return; 449 } 450 mLastKnownLauncher = launcherComponent; 451 mService.scheduleSaveUser(mUserId); 452 } 453 454 public ComponentName getCachedLauncher() { 455 return mCachedLauncher; 456 } 457 458 public void resetThrottling() { 459 for (int i = mPackages.size() - 1; i >= 0; i--) { 460 mPackages.valueAt(i).resetThrottling(); 461 } 462 } 463 464 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 465 pw.print(prefix); 466 pw.print("User: "); 467 pw.print(mUserId); 468 pw.print(" Known locales: "); 469 pw.print(mKnownLocales); 470 pw.print(" Last app scan: ["); 471 pw.print(mLastAppScanTime); 472 pw.print("] "); 473 pw.print(ShortcutService.formatTime(mLastAppScanTime)); 474 pw.print(" Last app scan FP: "); 475 pw.print(mLastAppScanOsFingerprint); 476 pw.println(); 477 478 prefix += prefix + " "; 479 480 pw.print(prefix); 481 pw.print("Cached launcher: "); 482 pw.print(mCachedLauncher); 483 pw.println(); 484 485 pw.print(prefix); 486 pw.print("Last known launcher: "); 487 pw.print(mLastKnownLauncher); 488 pw.println(); 489 490 for (int i = 0; i < mLaunchers.size(); i++) { 491 mLaunchers.valueAt(i).dump(pw, prefix); 492 } 493 494 for (int i = 0; i < mPackages.size(); i++) { 495 mPackages.valueAt(i).dump(pw, prefix); 496 } 497 498 pw.println(); 499 pw.print(prefix); 500 pw.println("Bitmap directories: "); 501 dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId)); 502 } 503 504 private void dumpDirectorySize(@NonNull PrintWriter pw, 505 @NonNull String prefix, File path) { 506 int numFiles = 0; 507 long size = 0; 508 final File[] children = path.listFiles(); 509 if (children != null) { 510 for (File child : path.listFiles()) { 511 if (child.isFile()) { 512 numFiles++; 513 size += child.length(); 514 } else if (child.isDirectory()) { 515 dumpDirectorySize(pw, prefix + " ", child); 516 } 517 } 518 } 519 pw.print(prefix); 520 pw.print("Path: "); 521 pw.print(path.getName()); 522 pw.print("/ has "); 523 pw.print(numFiles); 524 pw.print(" files, size="); 525 pw.print(size); 526 pw.print(" ("); 527 pw.print(Formatter.formatFileSize(mService.mContext, size)); 528 pw.println(")"); 529 } 530 531 public JSONObject dumpCheckin(boolean clear) throws JSONException { 532 final JSONObject result = new JSONObject(); 533 534 result.put(KEY_USER_ID, mUserId); 535 536 { 537 final JSONArray launchers = new JSONArray(); 538 for (int i = 0; i < mLaunchers.size(); i++) { 539 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear)); 540 } 541 result.put(KEY_LAUNCHERS, launchers); 542 } 543 544 { 545 final JSONArray packages = new JSONArray(); 546 for (int i = 0; i < mPackages.size(); i++) { 547 packages.put(mPackages.valueAt(i).dumpCheckin(clear)); 548 } 549 result.put(KEY_PACKAGES, packages); 550 } 551 552 return result; 553 } 554} 555