ShortcutUser.java revision 76269928e677725e2d9b28e2e3aa79961a60a1d0
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 private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time"; 61 private static final String KEY_USER_ID = "userId"; 62 private static final String KEY_LAUNCHERS = "launchers"; 63 private static final String KEY_PACKAGES = "packages"; 64 65 static final class PackageWithUser { 66 final int userId; 67 final String packageName; 68 69 private PackageWithUser(int userId, String packageName) { 70 this.userId = userId; 71 this.packageName = Preconditions.checkNotNull(packageName); 72 } 73 74 public static PackageWithUser of(int userId, String packageName) { 75 return new PackageWithUser(userId, packageName); 76 } 77 78 public static PackageWithUser of(ShortcutPackageItem spi) { 79 return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName()); 80 } 81 82 @Override 83 public int hashCode() { 84 return packageName.hashCode() ^ userId; 85 } 86 87 @Override 88 public boolean equals(Object obj) { 89 if (!(obj instanceof PackageWithUser)) { 90 return false; 91 } 92 final PackageWithUser that = (PackageWithUser) obj; 93 94 return userId == that.userId && packageName.equals(that.packageName); 95 } 96 97 @Override 98 public String toString() { 99 return String.format("[Package: %d, %s]", userId, packageName); 100 } 101 } 102 103 final ShortcutService mService; 104 105 @UserIdInt 106 private final int mUserId; 107 108 private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); 109 110 private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); 111 112 /** 113 * Last known launcher. It's used when the default launcher isn't set in PM -- i.e. 114 * when getHomeActivitiesAsUser() return null. We need it so that in this situation the 115 * previously default launcher can still access shortcuts. 116 */ 117 private ComponentName mLastKnownLauncher; 118 119 /** In-memory-cached default launcher. */ 120 private ComponentName mCachedLauncher; 121 122 private String mKnownLocales; 123 124 private long mLastAppScanTime; 125 126 public ShortcutUser(ShortcutService service, int userId) { 127 mService = service; 128 mUserId = userId; 129 } 130 131 public int getUserId() { 132 return mUserId; 133 } 134 135 public long getLastAppScanTime() { 136 return mLastAppScanTime; 137 } 138 139 public void setLastAppScanTime(long lastAppScanTime) { 140 mLastAppScanTime = lastAppScanTime; 141 } 142 143 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 144 // remove from it. 145 @VisibleForTesting 146 ArrayMap<String, ShortcutPackage> getAllPackagesForTest() { 147 return mPackages; 148 } 149 150 public boolean hasPackage(@NonNull String packageName) { 151 return mPackages.containsKey(packageName); 152 } 153 154 public ShortcutPackage removePackage(@NonNull String packageName) { 155 final ShortcutPackage removed = mPackages.remove(packageName); 156 157 mService.cleanupBitmapsForPackage(mUserId, packageName); 158 159 return removed; 160 } 161 162 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 163 // remove from it. 164 @VisibleForTesting 165 ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() { 166 return mLaunchers; 167 } 168 169 public void addLauncher(ShortcutLauncher launcher) { 170 mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), 171 launcher.getPackageName()), launcher); 172 } 173 174 @Nullable 175 public ShortcutLauncher removeLauncher( 176 @UserIdInt int packageUserId, @NonNull String packageName) { 177 return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); 178 } 179 180 @Nullable 181 public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) { 182 final ShortcutPackage ret = mPackages.get(packageName); 183 if (ret != null) { 184 ret.attemptToRestoreIfNeededAndSave(); 185 } 186 return ret; 187 } 188 189 @NonNull 190 public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { 191 ShortcutPackage ret = getPackageShortcutsIfExists(packageName); 192 if (ret == null) { 193 ret = new ShortcutPackage(this, mUserId, packageName); 194 mPackages.put(packageName, ret); 195 } 196 return ret; 197 } 198 199 @NonNull 200 public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName, 201 @UserIdInt int launcherUserId) { 202 final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); 203 ShortcutLauncher ret = mLaunchers.get(key); 204 if (ret == null) { 205 ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId); 206 mLaunchers.put(key, ret); 207 } else { 208 ret.attemptToRestoreIfNeededAndSave(); 209 } 210 return ret; 211 } 212 213 public void forAllPackages(Consumer<? super ShortcutPackage> callback) { 214 final int size = mPackages.size(); 215 for (int i = 0; i < size; i++) { 216 callback.accept(mPackages.valueAt(i)); 217 } 218 } 219 220 public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) { 221 final int size = mLaunchers.size(); 222 for (int i = 0; i < size; i++) { 223 callback.accept(mLaunchers.valueAt(i)); 224 } 225 } 226 227 public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) { 228 forAllLaunchers(callback); 229 forAllPackages(callback); 230 } 231 232 public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, 233 Consumer<ShortcutPackageItem> callback) { 234 forAllPackageItems(spi -> { 235 if ((spi.getPackageUserId() == packageUserId) 236 && spi.getPackageName().equals(packageName)) { 237 callback.accept(spi); 238 } 239 }); 240 } 241 242 /** 243 * Must be called at any entry points on {@link ShortcutManager} APIs to make sure the 244 * information on the package is up-to-date. 245 * 246 * We use broadcasts to handle locale changes and package changes, but because broadcasts 247 * are asynchronous, there's a chance a publisher calls getXxxShortcuts() after a certain event 248 * (e.g. system locale change) but shortcut manager hasn't finished processing the broadcast. 249 * 250 * So we call this method at all entry points from publishers to make sure we update all 251 * relevant information. 252 * 253 * Similar inconsistencies can happen when the launcher fetches shortcut information, but 254 * that's a less of an issue because for the launcher we report shortcut changes with 255 * callbacks. 256 */ 257 public void onCalledByPublisher(@NonNull String packageName) { 258 detectLocaleChange(); 259 rescanPackageIfNeeded(packageName, /*forceRescan=*/ false); 260 } 261 262 private String getKnownLocales() { 263 if (TextUtils.isEmpty(mKnownLocales)) { 264 mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId); 265 mService.scheduleSaveUser(mUserId); 266 } 267 return mKnownLocales; 268 } 269 270 /** 271 * Check to see if the system locale has changed, and if so, reset throttling 272 * and update resource strings. 273 */ 274 public void detectLocaleChange() { 275 final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId); 276 if (getKnownLocales().equals(currentLocales)) { 277 return; 278 } 279 if (ShortcutService.DEBUG) { 280 Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales 281 + " for user " + mUserId); 282 } 283 mKnownLocales = currentLocales; 284 285 forAllPackages(pkg -> { 286 pkg.resetRateLimiting(); 287 pkg.resolveResourceStrings(); 288 }); 289 290 mService.scheduleSaveUser(mUserId); 291 } 292 293 public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) { 294 final boolean isNewApp = !mPackages.containsKey(packageName); 295 296 final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName); 297 298 if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) { 299 if (isNewApp) { 300 mPackages.remove(packageName); 301 } 302 } 303 } 304 305 public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, 306 @UserIdInt int packageUserId) { 307 forPackageItem(packageName, packageUserId, spi -> { 308 spi.attemptToRestoreIfNeededAndSave(); 309 }); 310 } 311 312 public void saveToXml(XmlSerializer out, boolean forBackup) 313 throws IOException, XmlPullParserException { 314 out.startTag(null, TAG_ROOT); 315 316 ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales); 317 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, 318 mLastAppScanTime); 319 320 ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher); 321 322 // Can't use forEachPackageItem due to the checked exceptions. 323 { 324 final int size = mLaunchers.size(); 325 for (int i = 0; i < size; i++) { 326 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup); 327 } 328 } 329 { 330 final int size = mPackages.size(); 331 for (int i = 0; i < size; i++) { 332 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup); 333 } 334 } 335 336 out.endTag(null, TAG_ROOT); 337 } 338 339 private void saveShortcutPackageItem(XmlSerializer out, 340 ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { 341 if (forBackup) { 342 if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) { 343 return; // Don't save. 344 } 345 if (spi.getPackageUserId() != spi.getOwnerUserId()) { 346 return; // Don't save cross-user information. 347 } 348 } 349 spi.saveToXml(out, forBackup); 350 } 351 352 public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, 353 boolean fromBackup) throws IOException, XmlPullParserException { 354 final ShortcutUser ret = new ShortcutUser(s, userId); 355 356 ret.mKnownLocales = ShortcutService.parseStringAttribute(parser, 357 ATTR_KNOWN_LOCALES); 358 359 // If lastAppScanTime is in the future, that means the clock went backwards. 360 // Just scan all apps again. 361 final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, 362 ATTR_LAST_APP_SCAN_TIME); 363 final long currentTime = s.injectCurrentTimeMillis(); 364 ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; 365 366 final int outerDepth = parser.getDepth(); 367 int type; 368 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 369 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 370 if (type != XmlPullParser.START_TAG) { 371 continue; 372 } 373 final int depth = parser.getDepth(); 374 final String tag = parser.getName(); 375 376 if (depth == outerDepth + 1) { 377 switch (tag) { 378 case TAG_LAUNCHER: { 379 ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute( 380 parser, ATTR_VALUE); 381 continue; 382 } 383 case ShortcutPackage.TAG_ROOT: { 384 final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( 385 s, ret, parser, fromBackup); 386 387 // Don't use addShortcut(), we don't need to save the icon. 388 ret.mPackages.put(shortcuts.getPackageName(), shortcuts); 389 continue; 390 } 391 392 case ShortcutLauncher.TAG_ROOT: { 393 ret.addLauncher( 394 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); 395 continue; 396 } 397 } 398 } 399 ShortcutService.warnForInvalidTag(depth, tag); 400 } 401 return ret; 402 } 403 404 public ComponentName getLastKnownLauncher() { 405 return mLastKnownLauncher; 406 } 407 408 public void setLauncher(ComponentName launcherComponent) { 409 setLauncher(launcherComponent, /* allowPurgeLastKnown */ false); 410 } 411 412 /** Clears the launcher information without clearing the last known one */ 413 public void clearLauncher() { 414 setLauncher(null); 415 } 416 417 /** 418 * Clears the launcher information *with(* clearing the last known one; we do this witl 419 * "cmd shortcut clear-default-launcher". 420 */ 421 public void forceClearLauncher() { 422 setLauncher(null, /* allowPurgeLastKnown */ true); 423 } 424 425 private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) { 426 mCachedLauncher = launcherComponent; // Always update the in-memory cache. 427 428 if (Objects.equal(mLastKnownLauncher, launcherComponent)) { 429 return; 430 } 431 if (!allowPurgeLastKnown && launcherComponent == null) { 432 return; 433 } 434 mLastKnownLauncher = launcherComponent; 435 mService.scheduleSaveUser(mUserId); 436 } 437 438 public ComponentName getCachedLauncher() { 439 return mCachedLauncher; 440 } 441 442 public void resetThrottling() { 443 for (int i = mPackages.size() - 1; i >= 0; i--) { 444 mPackages.valueAt(i).resetThrottling(); 445 } 446 } 447 448 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 449 pw.print(prefix); 450 pw.print("User: "); 451 pw.print(mUserId); 452 pw.print(" Known locales: "); 453 pw.print(mKnownLocales); 454 pw.print(" Last app scan: ["); 455 pw.print(mLastAppScanTime); 456 pw.print("] "); 457 pw.print(ShortcutService.formatTime(mLastAppScanTime)); 458 pw.println(); 459 460 prefix += prefix + " "; 461 462 pw.print(prefix); 463 pw.print("Cached launcher: "); 464 pw.print(mCachedLauncher); 465 pw.println(); 466 467 pw.print(prefix); 468 pw.print("Last known launcher: "); 469 pw.print(mLastKnownLauncher); 470 pw.println(); 471 472 for (int i = 0; i < mLaunchers.size(); i++) { 473 mLaunchers.valueAt(i).dump(pw, prefix); 474 } 475 476 for (int i = 0; i < mPackages.size(); i++) { 477 mPackages.valueAt(i).dump(pw, prefix); 478 } 479 480 pw.println(); 481 pw.print(prefix); 482 pw.println("Bitmap directories: "); 483 dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId)); 484 } 485 486 private void dumpDirectorySize(@NonNull PrintWriter pw, 487 @NonNull String prefix, File path) { 488 int numFiles = 0; 489 long size = 0; 490 final File[] children = path.listFiles(); 491 if (children != null) { 492 for (File child : path.listFiles()) { 493 if (child.isFile()) { 494 numFiles++; 495 size += child.length(); 496 } else if (child.isDirectory()) { 497 dumpDirectorySize(pw, prefix + " ", child); 498 } 499 } 500 } 501 pw.print(prefix); 502 pw.print("Path: "); 503 pw.print(path.getName()); 504 pw.print("/ has "); 505 pw.print(numFiles); 506 pw.print(" files, size="); 507 pw.print(size); 508 pw.print(" ("); 509 pw.print(Formatter.formatFileSize(mService.mContext, size)); 510 pw.println(")"); 511 } 512 513 public JSONObject dumpCheckin(boolean clear) throws JSONException { 514 final JSONObject result = new JSONObject(); 515 516 result.put(KEY_USER_ID, mUserId); 517 518 { 519 final JSONArray launchers = new JSONArray(); 520 for (int i = 0; i < mLaunchers.size(); i++) { 521 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear)); 522 } 523 result.put(KEY_LAUNCHERS, launchers); 524 } 525 526 { 527 final JSONArray packages = new JSONArray(); 528 for (int i = 0; i < mPackages.size(); i++) { 529 packages.put(mPackages.valueAt(i).dumpCheckin(clear)); 530 } 531 result.put(KEY_PACKAGES, packages); 532 } 533 534 return result; 535 } 536} 537