2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.server.pm;
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;
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.util.Preconditions;
33 import libcore.util.Objects;
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;
43 import java.io.IOException;
44 import java.io.PrintWriter;
45 import java.util.function.Consumer;
48 * User information used by {@link ShortcutService}.
50 * All methods should be guarded by {@code #mService.mLock}.
53 private static final String TAG = ShortcutService.TAG;
55 static final String TAG_ROOT = "user";
56 private static final String TAG_LAUNCHER = "launcher";
58 private static final String ATTR_VALUE = "value";
59 private static final String ATTR_KNOWN_LOCALES = "locales";
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";
67 static final class PackageWithUser {
69 final String packageName;
71 private PackageWithUser(int userId, String packageName) {
73 this.packageName = Preconditions.checkNotNull(packageName);
76 public static PackageWithUser of(int userId, String packageName) {
77 return new PackageWithUser(userId, packageName);
80 public static PackageWithUser of(ShortcutPackageItem spi) {
81 return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
85 public int hashCode() {
86 return packageName.hashCode() ^ userId;
90 public boolean equals(Object obj) {
91 if (!(obj instanceof PackageWithUser)) {
94 final PackageWithUser that = (PackageWithUser) obj;
96 return userId == that.userId && packageName.equals(that.packageName);
100 public String toString() {
101 return String.format("[Package: %d, %s]", userId, packageName);
105 final ShortcutService mService;
108 private final int mUserId;
110 private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
112 private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
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.
119 private ComponentName mLastKnownLauncher;
121 /** In-memory-cached default launcher. */
122 private ComponentName mCachedLauncher;
124 private String mKnownLocales;
126 private long mLastAppScanTime;
128 public ShortcutUser(ShortcutService service, int userId) {
133 public int getUserId() {
137 public long getLastAppScanTime() {
138 return mLastAppScanTime;
141 public void setLastAppScanTime(long lastAppScanTime) {
142 mLastAppScanTime = lastAppScanTime;
145 // We don't expose this directly to non-test code because only ShortcutUser should add to/
148 ArrayMap<String, ShortcutPackage> getAllPackagesForTest() {
152 public boolean hasPackage(@NonNull String packageName) {
153 return mPackages.containsKey(packageName);
156 public ShortcutPackage removePackage(@NonNull String packageName) {
157 final ShortcutPackage removed = mPackages.remove(packageName);
159 mService.cleanupBitmapsForPackage(mUserId, packageName);
164 // We don't expose this directly to non-test code because only ShortcutUser should add to/
167 ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
171 public void addLauncher(ShortcutLauncher launcher) {
172 mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
173 launcher.getPackageName()), launcher);
177 public ShortcutLauncher removeLauncher(
178 @UserIdInt int packageUserId, @NonNull String packageName) {
179 return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
183 public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
184 final ShortcutPackage ret = mPackages.get(packageName);
186 ret.attemptToRestoreIfNeededAndSave();
192 public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
193 ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
195 ret = new ShortcutPackage(this, mUserId, packageName);
196 mPackages.put(packageName, ret);
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);
207 ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
208 mLaunchers.put(key, ret);
210 ret.attemptToRestoreIfNeededAndSave();
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));
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));
229 public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) {
230 forAllLaunchers(callback);
231 forAllPackages(callback);
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);
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.
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.
252 * So we call this method at all entry points from publishers to make sure we update all
253 * relevant information.
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
259 public void onCalledByPublisher(@NonNull String packageName) {
260 detectLocaleChange();
261 rescanPackageIfNeeded(packageName, /*forceRescan=*/ false);
264 private String getKnownLocales() {
265 if (TextUtils.isEmpty(mKnownLocales)) {
266 mKnownLocales = mService.injectGetLocaleTagsForUser(mUserId);
267 mService.scheduleSaveUser(mUserId);
269 return mKnownLocales;
273 * Check to see if the system locale has changed, and if so, reset throttling
274 * and update resource strings.
276 public void detectLocaleChange() {
277 final String currentLocales = mService.injectGetLocaleTagsForUser(mUserId);
278 if (getKnownLocales().equals(currentLocales)) {
281 if (ShortcutService.DEBUG) {
282 Slog.d(TAG, "Locale changed from " + currentLocales + " to " + mKnownLocales
283 + " for user " + mUserId);
285 mKnownLocales = currentLocales;
287 forAllPackages(pkg -> {
288 pkg.resetRateLimiting();
289 pkg.resolveResourceStrings();
292 mService.scheduleSaveUser(mUserId);
295 public void rescanPackageIfNeeded(@NonNull String packageName, boolean forceRescan) {
296 final boolean isNewApp = !mPackages.containsKey(packageName);
298 final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
300 if (!shortcutPackage.rescanPackageIfNeeded(isNewApp, forceRescan)) {
302 mPackages.remove(packageName);
307 public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
308 @UserIdInt int packageUserId) {
309 forPackageItem(packageName, packageUserId, spi -> {
310 spi.attemptToRestoreIfNeededAndSave();
314 public void saveToXml(XmlSerializer out, boolean forBackup)
315 throws IOException, XmlPullParserException {
316 out.startTag(null, TAG_ROOT);
318 ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALES, mKnownLocales);
319 ShortcutService.writeAttr(out, ATTR_LAST_APP_SCAN_TIME,
322 ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLastKnownLauncher);
324 // Can't use forEachPackageItem due to the checked exceptions.
326 final int size = mLaunchers.size();
327 for (int i = 0; i < size; i++) {
328 saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
332 final int size = mPackages.size();
333 for (int i = 0; i < size; i++) {
334 saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
338 out.endTag(null, TAG_ROOT);
341 private void saveShortcutPackageItem(XmlSerializer out,
342 ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
344 if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
345 return; // Don't save.
347 if (spi.getPackageUserId() != spi.getOwnerUserId()) {
348 return; // Don't save cross-user information.
351 spi.saveToXml(out, forBackup);
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);
358 ret.mKnownLocales = ShortcutService.parseStringAttribute(parser,
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;
368 final int outerDepth = parser.getDepth();
370 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
371 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
372 if (type != XmlPullParser.START_TAG) {
375 final int depth = parser.getDepth();
376 final String tag = parser.getName();
378 if (depth == outerDepth + 1) {
381 ret.mLastKnownLauncher = ShortcutService.parseComponentNameAttribute(
385 case ShortcutPackage.TAG_ROOT: {
386 final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(
387 s, ret, parser, fromBackup);
389 // Don't use addShortcut(), we don't need to save the icon.
390 ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
394 case ShortcutLauncher.TAG_ROOT: {
396 ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup));
401 ShortcutService.warnForInvalidTag(depth, tag);
406 public ComponentName getLastKnownLauncher() {
407 return mLastKnownLauncher;
410 public void setLauncher(ComponentName launcherComponent) {
411 setLauncher(launcherComponent, /* allowPurgeLastKnown */ false);
414 /** Clears the launcher information without clearing the last known one */
415 public void clearLauncher() {
420 * Clears the launcher information *with(* clearing the last known one; we do this witl
421 * "cmd shortcut clear-default-launcher".
423 public void forceClearLauncher() {
424 setLauncher(null, /* allowPurgeLastKnown */ true);
427 private void setLauncher(ComponentName launcherComponent, boolean allowPurgeLastKnown) {
428 mCachedLauncher = launcherComponent; // Always update the in-memory cache.
430 if (Objects.equal(mLastKnownLauncher, launcherComponent)) {
433 if (!allowPurgeLastKnown && launcherComponent == null) {
436 mLastKnownLauncher = launcherComponent;
437 mService.scheduleSaveUser(mUserId);
440 public ComponentName getCachedLauncher() {
441 return mCachedLauncher;
444 public void resetThrottling() {
445 for (int i = mPackages.size() - 1; i >= 0; i--) {
446 mPackages.valueAt(i).resetThrottling();
450 public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
454 pw.print(" Known locales: ");
455 pw.print(mKnownLocales);
456 pw.print(" Last app scan: [");
457 pw.print(mLastAppScanTime);
459 pw.print(ShortcutService.formatTime(mLastAppScanTime));
462 prefix += prefix + " ";
465 pw.print("Cached launcher: ");
466 pw.print(mCachedLauncher);
470 pw.print("Last known launcher: ");
471 pw.print(mLastKnownLauncher);
474 for (int i = 0; i < mLaunchers.size(); i++) {
475 mLaunchers.valueAt(i).dump(pw, prefix);
478 for (int i = 0; i < mPackages.size(); i++) {
479 mPackages.valueAt(i).dump(pw, prefix);
484 pw.println("Bitmap directories: ");
485 dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId));
488 private void dumpDirectorySize(@NonNull PrintWriter pw,
489 @NonNull String prefix, File path) {
492 final File[] children = path.listFiles();
493 if (children != null) {
494 for (File child : path.listFiles()) {
495 if (child.isFile()) {
497 size += child.length();
498 } else if (child.isDirectory()) {
499 dumpDirectorySize(pw, prefix + " ", child);
505 pw.print(path.getName());
508 pw.print(" files, size=");
511 pw.print(Formatter.formatFileSize(mService.mContext, size));
515 public JSONObject dumpCheckin(boolean clear) throws JSONException {
516 final JSONObject result = new JSONObject();
518 result.put(KEY_USER_ID, mUserId);
521 final JSONArray launchers = new JSONArray();
522 for (int i = 0; i < mLaunchers.size(); i++) {
523 launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear));
525 result.put(KEY_LAUNCHERS, launchers);
529 final JSONArray packages = new JSONArray();
530 for (int i = 0; i < mPackages.size(); i++) {
531 packages.put(mPackages.valueAt(i).dumpCheckin(clear));
533 result.put(KEY_PACKAGES, packages);