ShortcutUser.java revision b08790c3b2f3bdb0c2e2f7ff46e4584fb1127769
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.text.format.Formatter; 23import android.util.ArrayMap; 24import android.util.Slog; 25import android.util.SparseArray; 26 27import com.android.internal.annotations.GuardedBy; 28import com.android.internal.annotations.VisibleForTesting; 29import com.android.internal.util.Preconditions; 30 31import libcore.util.Objects; 32 33import org.xmlpull.v1.XmlPullParser; 34import org.xmlpull.v1.XmlPullParserException; 35import org.xmlpull.v1.XmlSerializer; 36 37import java.io.File; 38import java.io.IOException; 39import java.io.PrintWriter; 40import java.util.function.Consumer; 41 42/** 43 * User information used by {@link ShortcutService}. 44 * 45 * All methods should be guarded by {@code #mService.mLock}. 46 */ 47class ShortcutUser { 48 private static final String TAG = ShortcutService.TAG; 49 50 static final String TAG_ROOT = "user"; 51 private static final String TAG_LAUNCHER = "launcher"; 52 53 private static final String ATTR_VALUE = "value"; 54 private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no"; 55 private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time"; 56 57 static final class PackageWithUser { 58 final int userId; 59 final String packageName; 60 61 private PackageWithUser(int userId, String packageName) { 62 this.userId = userId; 63 this.packageName = Preconditions.checkNotNull(packageName); 64 } 65 66 public static PackageWithUser of(int userId, String packageName) { 67 return new PackageWithUser(userId, packageName); 68 } 69 70 public static PackageWithUser of(ShortcutPackageItem spi) { 71 return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName()); 72 } 73 74 @Override 75 public int hashCode() { 76 return packageName.hashCode() ^ userId; 77 } 78 79 @Override 80 public boolean equals(Object obj) { 81 if (!(obj instanceof PackageWithUser)) { 82 return false; 83 } 84 final PackageWithUser that = (PackageWithUser) obj; 85 86 return userId == that.userId && packageName.equals(that.packageName); 87 } 88 89 @Override 90 public String toString() { 91 return String.format("[Package: %d, %s]", userId, packageName); 92 } 93 } 94 95 final ShortcutService mService; 96 97 @UserIdInt 98 private final int mUserId; 99 100 private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); 101 102 private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); 103 104 /** Default launcher that can access the launcher apps APIs. */ 105 private ComponentName mDefaultLauncherComponent; 106 107 private long mKnownLocaleChangeSequenceNumber; 108 109 private long mLastAppScanTime; 110 111 public ShortcutUser(ShortcutService service, int userId) { 112 mService = service; 113 mUserId = userId; 114 } 115 116 public int getUserId() { 117 return mUserId; 118 } 119 120 public long getLastAppScanTime() { 121 return mLastAppScanTime; 122 } 123 124 public void setLastAppScanTime(long lastAppScanTime) { 125 mLastAppScanTime = lastAppScanTime; 126 } 127 128 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 129 // remove from it. 130 @VisibleForTesting 131 ArrayMap<String, ShortcutPackage> getAllPackagesForTest() { 132 return mPackages; 133 } 134 135 public boolean hasPackage(@NonNull String packageName) { 136 return mPackages.containsKey(packageName); 137 } 138 139 public ShortcutPackage removePackage(@NonNull String packageName) { 140 final ShortcutPackage removed = mPackages.remove(packageName); 141 142 mService.cleanupBitmapsForPackage(mUserId, packageName); 143 144 return removed; 145 } 146 147 // We don't expose this directly to non-test code because only ShortcutUser should add to/ 148 // remove from it. 149 @VisibleForTesting 150 ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() { 151 return mLaunchers; 152 } 153 154 public void addLauncher(ShortcutLauncher launcher) { 155 mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), 156 launcher.getPackageName()), launcher); 157 } 158 159 @Nullable 160 public ShortcutLauncher removeLauncher( 161 @UserIdInt int packageUserId, @NonNull String packageName) { 162 return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); 163 } 164 165 @Nullable 166 public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) { 167 final ShortcutPackage ret = mPackages.get(packageName); 168 if (ret != null) { 169 ret.attemptToRestoreIfNeededAndSave(); 170 } 171 return ret; 172 } 173 174 @NonNull 175 public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { 176 ShortcutPackage ret = getPackageShortcutsIfExists(packageName); 177 if (ret == null) { 178 ret = new ShortcutPackage(this, mUserId, packageName); 179 mPackages.put(packageName, ret); 180 } 181 return ret; 182 } 183 184 @NonNull 185 public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName, 186 @UserIdInt int launcherUserId) { 187 final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); 188 ShortcutLauncher ret = mLaunchers.get(key); 189 if (ret == null) { 190 ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId); 191 mLaunchers.put(key, ret); 192 } else { 193 ret.attemptToRestoreIfNeededAndSave(); 194 } 195 return ret; 196 } 197 198 public void forAllPackages(Consumer<? super ShortcutPackage> callback) { 199 final int size = mPackages.size(); 200 for (int i = 0; i < size; i++) { 201 callback.accept(mPackages.valueAt(i)); 202 } 203 } 204 205 public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) { 206 final int size = mLaunchers.size(); 207 for (int i = 0; i < size; i++) { 208 callback.accept(mLaunchers.valueAt(i)); 209 } 210 } 211 212 public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) { 213 forAllLaunchers(callback); 214 forAllPackages(callback); 215 } 216 217 public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, 218 Consumer<ShortcutPackageItem> callback) { 219 forAllPackageItems(spi -> { 220 if ((spi.getPackageUserId() == packageUserId) 221 && spi.getPackageName().equals(packageName)) { 222 callback.accept(spi); 223 } 224 }); 225 } 226 227 /** 228 * Reset all throttling counters for all packages, if there has been a system locale change. 229 */ 230 public void resetThrottlingIfNeeded() { 231 final long currentNo = mService.getLocaleChangeSequenceNumber(); 232 if (mKnownLocaleChangeSequenceNumber < currentNo) { 233 if (ShortcutService.DEBUG) { 234 Slog.d(TAG, "LocaleChange detected for user " + mUserId); 235 } 236 237 mKnownLocaleChangeSequenceNumber = currentNo; 238 239 forAllPackages(p -> p.resetRateLimiting()); 240 241 mService.scheduleSaveUser(mUserId); 242 } 243 } 244 245 public void handlePackageAddedOrUpdated(@NonNull String packageName, boolean forceRescan) { 246 final boolean isNewApp = !mPackages.containsKey(packageName); 247 248 final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName); 249 250 if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp, forceRescan)) { 251 if (isNewApp) { 252 mPackages.remove(packageName); 253 } 254 } 255 } 256 257 public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, 258 @UserIdInt int packageUserId) { 259 forPackageItem(packageName, packageUserId, spi -> { 260 spi.attemptToRestoreIfNeededAndSave(); 261 }); 262 } 263 264 public void saveToXml(XmlSerializer out, boolean forBackup) 265 throws IOException, XmlPullParserException { 266 out.startTag(null, TAG_ROOT); 267 268 ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER, 269 mKnownLocaleChangeSequenceNumber); 270 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME, 271 mLastAppScanTime); 272 273 ShortcutService.writeTagValue(out, TAG_LAUNCHER, 274 mDefaultLauncherComponent); 275 276 // Can't use forEachPackageItem due to the checked exceptions. 277 { 278 final int size = mLaunchers.size(); 279 for (int i = 0; i < size; i++) { 280 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup); 281 } 282 } 283 { 284 final int size = mPackages.size(); 285 for (int i = 0; i < size; i++) { 286 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup); 287 } 288 } 289 290 out.endTag(null, TAG_ROOT); 291 } 292 293 private void saveShortcutPackageItem(XmlSerializer out, 294 ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { 295 if (forBackup) { 296 if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) { 297 return; // Don't save. 298 } 299 if (spi.getPackageUserId() != spi.getOwnerUserId()) { 300 return; // Don't save cross-user information. 301 } 302 } 303 spi.saveToXml(out, forBackup); 304 } 305 306 public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId, 307 boolean fromBackup) throws IOException, XmlPullParserException { 308 final ShortcutUser ret = new ShortcutUser(s, userId); 309 310 ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser, 311 ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER); 312 313 // If lastAppScanTime is in the future, that means the clock went backwards. 314 // Just scan all apps again. 315 final long lastAppScanTime = ShortcutService.parseLongAttribute(parser, 316 ATTR_LAST_APP_SCAN_TIME); 317 final long currentTime = s.injectCurrentTimeMillis(); 318 ret.mLastAppScanTime = lastAppScanTime < currentTime ? lastAppScanTime : 0; 319 320 final int outerDepth = parser.getDepth(); 321 int type; 322 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 323 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 324 if (type != XmlPullParser.START_TAG) { 325 continue; 326 } 327 final int depth = parser.getDepth(); 328 final String tag = parser.getName(); 329 330 if (depth == outerDepth + 1) { 331 switch (tag) { 332 case TAG_LAUNCHER: { 333 ret.mDefaultLauncherComponent = ShortcutService.parseComponentNameAttribute( 334 parser, ATTR_VALUE); 335 continue; 336 } 337 case ShortcutPackage.TAG_ROOT: { 338 final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( 339 s, ret, parser, fromBackup); 340 341 // Don't use addShortcut(), we don't need to save the icon. 342 ret.mPackages.put(shortcuts.getPackageName(), shortcuts); 343 continue; 344 } 345 346 case ShortcutLauncher.TAG_ROOT: { 347 ret.addLauncher( 348 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); 349 continue; 350 } 351 } 352 } 353 ShortcutService.warnForInvalidTag(depth, tag); 354 } 355 return ret; 356 } 357 358 public ComponentName getDefaultLauncherComponent() { 359 return mDefaultLauncherComponent; 360 } 361 362 public void setDefaultLauncherComponent(ComponentName launcherComponent) { 363 if (Objects.equal(mDefaultLauncherComponent, launcherComponent)) { 364 return; 365 } 366 mDefaultLauncherComponent = launcherComponent; 367 mService.scheduleSaveUser(mUserId); 368 } 369 370 public void resetThrottling() { 371 for (int i = mPackages.size() - 1; i >= 0; i--) { 372 mPackages.valueAt(i).resetThrottling(); 373 } 374 } 375 376 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 377 pw.print(prefix); 378 pw.print("User: "); 379 pw.print(mUserId); 380 pw.print(" Known locale seq#: "); 381 pw.print(mKnownLocaleChangeSequenceNumber); 382 pw.print(" Last app scan: ["); 383 pw.print(mLastAppScanTime); 384 pw.print("] "); 385 pw.print(ShortcutService.formatTime(mLastAppScanTime)); 386 pw.println(); 387 388 prefix += prefix + " "; 389 390 pw.print(prefix); 391 pw.print("Default launcher: "); 392 pw.print(mDefaultLauncherComponent); 393 pw.println(); 394 395 for (int i = 0; i < mLaunchers.size(); i++) { 396 mLaunchers.valueAt(i).dump(pw, prefix); 397 } 398 399 for (int i = 0; i < mPackages.size(); i++) { 400 mPackages.valueAt(i).dump(pw, prefix); 401 } 402 403 pw.println(); 404 pw.print(prefix); 405 pw.println("Bitmap directories: "); 406 dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId)); 407 } 408 409 private void dumpDirectorySize(@NonNull PrintWriter pw, 410 @NonNull String prefix, File path) { 411 int numFiles = 0; 412 long size = 0; 413 final File[] children = path.listFiles(); 414 if (children != null) { 415 for (File child : path.listFiles()) { 416 if (child.isFile()) { 417 numFiles++; 418 size += child.length(); 419 } else if (child.isDirectory()) { 420 dumpDirectorySize(pw, prefix + " ", child); 421 } 422 } 423 } 424 pw.print(prefix); 425 pw.print("Path: "); 426 pw.print(path.getName()); 427 pw.print("/ has "); 428 pw.print(numFiles); 429 pw.print(" files, size="); 430 pw.print(size); 431 pw.print(" ("); 432 pw.print(Formatter.formatFileSize(mService.mContext, size)); 433 pw.println(")"); 434 } 435} 436