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