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