diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java new file mode 100644 index 0000000000000..f1920c7e8e227 --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.pm.ShortcutInfo; +import android.util.ArrayMap; +import android.util.ArraySet; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +/** + * Launcher information used by {@link ShortcutService}. + */ +class ShortcutLauncher { + private static final String TAG = ShortcutService.TAG; + + static final String TAG_ROOT = "launcher-pins"; + + private static final String TAG_PACKAGE = "package"; + private static final String TAG_PIN = "pin"; + + private static final String ATTR_VALUE = "value"; + private static final String ATTR_PACKAGE_NAME = "package-name"; + + @UserIdInt + final int mUserId; + + @NonNull + final String mPackageName; + + /** + * Package name -> IDs. + */ + final private ArrayMap> mPinnedShortcuts = new ArrayMap<>(); + + ShortcutLauncher(@UserIdInt int userId, @NonNull String packageName) { + mUserId = userId; + mPackageName = packageName; + } + + public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName, + @NonNull List ids) { + final int idSize = ids.size(); + if (idSize == 0) { + mPinnedShortcuts.remove(packageName); + } else { + final ArraySet prevSet = mPinnedShortcuts.get(packageName); + + // Pin shortcuts. Make sure only pin the ones that were visible to the caller. + // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here. + + final ShortcutPackage packageShortcuts = + s.getPackageShortcutsLocked(packageName, mUserId); + final ArraySet newSet = new ArraySet<>(); + + for (int i = 0; i < idSize; i++) { + final String id = ids.get(i); + final ShortcutInfo si = packageShortcuts.findShortcutById(id); + if (si == null) { + continue; + } + if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) { + newSet.add(id); + } + } + mPinnedShortcuts.put(packageName, newSet); + } + s.getPackageShortcutsLocked(packageName, mUserId).refreshPinnedFlags(s); + } + + /** + * Return the pinned shortcut IDs for the publisher package. + */ + public ArraySet getPinnedShortcutIds(@NonNull String packageName) { + return mPinnedShortcuts.get(packageName); + } + + boolean cleanUpPackage(String packageName) { + return mPinnedShortcuts.remove(packageName) != null; + } + + /** + * Persist. + */ + public void saveToXml(XmlSerializer out) throws IOException { + final int size = mPinnedShortcuts.size(); + if (size == 0) { + return; // Nothing to write. + } + + out.startTag(null, TAG_ROOT); + ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, + mPackageName); + + for (int i = 0; i < size; i++) { + out.startTag(null, TAG_PACKAGE); + ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, + mPinnedShortcuts.keyAt(i)); + + final ArraySet ids = mPinnedShortcuts.valueAt(i); + final int idSize = ids.size(); + for (int j = 0; j < idSize; j++) { + ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j)); + } + out.endTag(null, TAG_PACKAGE); + } + + out.endTag(null, TAG_ROOT); + } + + /** + * Load. + */ + public static ShortcutLauncher loadFromXml(XmlPullParser parser, int userId) + throws IOException, XmlPullParserException { + final String launcherPackageName = ShortcutService.parseStringAttribute(parser, + ATTR_PACKAGE_NAME); + + final ShortcutLauncher ret = new ShortcutLauncher(userId, launcherPackageName); + + ArraySet ids = null; + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + switch (tag) { + case TAG_PACKAGE: { + final String packageName = ShortcutService.parseStringAttribute(parser, + ATTR_PACKAGE_NAME); + ids = new ArraySet<>(); + ret.mPinnedShortcuts.put(packageName, ids); + continue; + } + case TAG_PIN: { + ids.add(ShortcutService.parseStringAttribute(parser, + ATTR_VALUE)); + continue; + } + } + throw ShortcutService.throwForInvalidTag(depth, tag); + } + return ret; + } + + public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(); + + pw.print(prefix); + pw.print("Launcher: "); + pw.print(mPackageName); + pw.println(); + + final int size = mPinnedShortcuts.size(); + for (int i = 0; i < size; i++) { + pw.println(); + + pw.print(prefix); + pw.print(" "); + pw.print("Package: "); + pw.println(mPinnedShortcuts.keyAt(i)); + + final ArraySet ids = mPinnedShortcuts.valueAt(i); + final int idSize = ids.size(); + + for (int j = 0; j < idSize; j++) { + pw.print(prefix); + pw.print(" "); + pw.print(ids.valueAt(j)); + pw.println(); + } + } + } +} diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java new file mode 100644 index 0000000000000..d6142510015e6 --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.os.PersistableBundle; +import android.text.format.Formatter; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Slog; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * Package information used by {@link ShortcutService}. + */ +class ShortcutPackage { + private static final String TAG = ShortcutService.TAG; + + static final String TAG_ROOT = "package"; + private static final String TAG_INTENT_EXTRAS = "intent-extras"; + private static final String TAG_EXTRAS = "extras"; + private static final String TAG_SHORTCUT = "shortcut"; + + private static final String ATTR_NAME = "name"; + private static final String ATTR_DYNAMIC_COUNT = "dynamic-count"; + private static final String ATTR_CALL_COUNT = "call-count"; + private static final String ATTR_LAST_RESET = "last-reset"; + private static final String ATTR_ID = "id"; + private static final String ATTR_ACTIVITY = "activity"; + private static final String ATTR_TITLE = "title"; + private static final String ATTR_INTENT = "intent"; + private static final String ATTR_WEIGHT = "weight"; + private static final String ATTR_TIMESTAMP = "timestamp"; + private static final String ATTR_FLAGS = "flags"; + private static final String ATTR_ICON_RES = "icon-res"; + private static final String ATTR_BITMAP_PATH = "bitmap-path"; + + @UserIdInt + final int mUserId; + + @NonNull + final String mPackageName; + + /** + * All the shortcuts from the package, keyed on IDs. + */ + final private ArrayMap mShortcuts = new ArrayMap<>(); + + /** + * # of dynamic shortcuts. + */ + private int mDynamicShortcutCount = 0; + + /** + * # of times the package has called rate-limited APIs. + */ + private int mApiCallCount; + + /** + * When {@link #mApiCallCount} was reset last time. + */ + private long mLastResetTime; + + ShortcutPackage(int userId, String packageName) { + mUserId = userId; + mPackageName = packageName; + } + + @Nullable + public ShortcutInfo findShortcutById(String id) { + return mShortcuts.get(id); + } + + private ShortcutInfo deleteShortcut(@NonNull ShortcutService s, + @NonNull String id) { + final ShortcutInfo shortcut = mShortcuts.remove(id); + if (shortcut != null) { + s.removeIcon(mUserId, shortcut); + shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED); + } + return shortcut; + } + + void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) { + deleteShortcut(s, newShortcut.getId()); + s.saveIconAndFixUpShortcut(mUserId, newShortcut); + mShortcuts.put(newShortcut.getId(), newShortcut); + } + + /** + * Add a shortcut, or update one with the same ID, with taking over existing flags. + * + * It checks the max number of dynamic shortcuts. + */ + public void addDynamicShortcut(@NonNull ShortcutService s, + @NonNull ShortcutInfo newShortcut) { + newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); + + final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId()); + + final boolean wasPinned; + final int newDynamicCount; + + if (oldShortcut == null) { + wasPinned = false; + newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut. + } else { + wasPinned = oldShortcut.isPinned(); + if (oldShortcut.isDynamic()) { + newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut. + } else { + newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut. + } + } + + // Make sure there's still room. + s.enforceMaxDynamicShortcuts(newDynamicCount); + + // Okay, make it dynamic and add. + if (wasPinned) { + newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); + } + + addShortcut(s, newShortcut); + mDynamicShortcutCount = newDynamicCount; + } + + /** + * Remove all shortcuts that aren't pinned nor dynamic. + */ + private void removeOrphans(@NonNull ShortcutService s) { + ArrayList removeList = null; // Lazily initialize. + + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + final ShortcutInfo si = mShortcuts.valueAt(i); + + if (si.isPinned() || si.isDynamic()) continue; + + if (removeList == null) { + removeList = new ArrayList<>(); + } + removeList.add(si.getId()); + } + if (removeList != null) { + for (int i = removeList.size() - 1; i >= 0; i--) { + deleteShortcut(s, removeList.get(i)); + } + } + } + + /** + * Remove all dynamic shortcuts. + */ + public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) { + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC); + } + removeOrphans(s); + mDynamicShortcutCount = 0; + } + + /** + * Remove a dynamic shortcut by ID. + */ + public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) { + final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId); + + if (oldShortcut == null) { + return; + } + if (oldShortcut.isDynamic()) { + mDynamicShortcutCount--; + } + if (oldShortcut.isPinned()) { + oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC); + } else { + deleteShortcut(s, shortcutId); + } + } + + /** + * Called after a launcher updates the pinned set. For each shortcut in this package, + * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it. + * + *

Then remove all shortcuts that are not dynamic and no longer pinned either. + */ + public void refreshPinnedFlags(@NonNull ShortcutService s) { + // First, un-pin all shortcuts + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED); + } + + // Then, for the pinned set for each launcher, set the pin flag one by one. + final ArrayMap launchers = + s.getUserShortcutsLocked(mUserId).getLaunchers(); + + for (int l = launchers.size() - 1; l >= 0; l--) { + final ShortcutLauncher launcherShortcuts = launchers.valueAt(l); + final ArraySet pinned = launcherShortcuts.getPinnedShortcutIds(mPackageName); + + if (pinned == null || pinned.size() == 0) { + continue; + } + for (int i = pinned.size() - 1; i >= 0; i--) { + final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i)); + if (si == null) { + s.wtf("Shortcut not found"); + } else { + si.addFlags(ShortcutInfo.FLAG_PINNED); + } + } + } + + // Lastly, remove the ones that are no longer pinned nor dynamic. + removeOrphans(s); + } + + /** + * Number of calls that the caller has made, since the last reset. + */ + public int getApiCallCount(@NonNull ShortcutService s) { + final long last = s.getLastResetTimeLocked(); + + final long now = s.injectCurrentTimeMillis(); + if (ShortcutService.isClockValid(now) && mLastResetTime > now) { + Slog.w(TAG, "Clock rewound"); + // Clock rewound. + mLastResetTime = now; + mApiCallCount = 0; + return mApiCallCount; + } + + // If not reset yet, then reset. + if (mLastResetTime < last) { + if (ShortcutService.DEBUG) { + Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting", + mLastResetTime, now, last)); + } + mApiCallCount = 0; + mLastResetTime = last; + } + return mApiCallCount; + } + + /** + * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount} + * and return true. Otherwise just return false. + */ + public boolean tryApiCall(@NonNull ShortcutService s) { + if (getApiCallCount(s) >= s.mMaxDailyUpdates) { + return false; + } + mApiCallCount++; + return true; + } + + public void resetRateLimitingForCommandLine() { + mApiCallCount = 0; + mLastResetTime = 0; + } + + /** + * Find all shortcuts that match {@code query}. + */ + public void findAll(@NonNull ShortcutService s, @NonNull List result, + @Nullable Predicate query, int cloneFlag, + @Nullable String callingLauncher) { + + // Set of pinned shortcuts by the calling launcher. + final ArraySet pinnedByCallerSet = (callingLauncher == null) ? null + : s.getLauncherShortcuts(callingLauncher, mUserId) + .getPinnedShortcutIds(mPackageName); + + for (int i = 0; i < mShortcuts.size(); i++) { + final ShortcutInfo si = mShortcuts.valueAt(i); + + // If it's called by non-launcher (i.e. publisher, always include -> true. + // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned + // it. + final boolean isPinnedByCaller = (callingLauncher == null) + || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId())); + if (!si.isDynamic()) { + if (!si.isPinned()) { + s.wtf("Shortcut not pinned here"); + continue; + } + if (!isPinnedByCaller) { + continue; + } + } + final ShortcutInfo clone = si.clone(cloneFlag); + // Fix up isPinned for the caller. Note we need to do it before the "test" callback, + // since it may check isPinned. + if (!isPinnedByCaller) { + clone.clearFlags(ShortcutInfo.FLAG_PINNED); + } + if (query == null || query.test(clone)) { + result.add(clone); + } + } + } + + public void resetThrottling() { + mApiCallCount = 0; + } + + public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) { + pw.println(); + + pw.print(prefix); + pw.print("Package: "); + pw.print(mPackageName); + pw.println(); + + pw.print(prefix); + pw.print(" "); + pw.print("Calls: "); + pw.print(getApiCallCount(s)); + pw.println(); + + // This should be after getApiCallCount(), which may update it. + pw.print(prefix); + pw.print(" "); + pw.print("Last reset: ["); + pw.print(mLastResetTime); + pw.print("] "); + pw.print(s.formatTime(mLastResetTime)); + pw.println(); + + pw.println(" Shortcuts:"); + long totalBitmapSize = 0; + final ArrayMap shortcuts = mShortcuts; + final int size = shortcuts.size(); + for (int i = 0; i < size; i++) { + final ShortcutInfo si = shortcuts.valueAt(i); + pw.print(" "); + pw.println(si.toInsecureString()); + if (si.getBitmapPath() != null) { + final long len = new File(si.getBitmapPath()).length(); + pw.print(" "); + pw.print("bitmap size="); + pw.println(len); + + totalBitmapSize += len; + } + } + pw.print(prefix); + pw.print(" "); + pw.print("Total bitmap size: "); + pw.print(totalBitmapSize); + pw.print(" ("); + pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize)); + pw.println(")"); + } + + public void saveToXml(@NonNull XmlSerializer out) throws IOException, XmlPullParserException { + final int size = mShortcuts.size(); + + if (size == 0 && mApiCallCount == 0) { + return; // nothing to write. + } + + out.startTag(null, TAG_ROOT); + + ShortcutService.writeAttr(out, ATTR_NAME, mPackageName); + ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount); + ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); + ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); + + for (int j = 0; j < size; j++) { + saveShortcut(out, mShortcuts.valueAt(j)); + } + + out.endTag(null, TAG_ROOT); + } + + private static void saveShortcut(XmlSerializer out, ShortcutInfo si) + throws IOException, XmlPullParserException { + out.startTag(null, TAG_SHORTCUT); + ShortcutService.writeAttr(out, ATTR_ID, si.getId()); + // writeAttr(out, "package", si.getPackageName()); // not needed + ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent()); + // writeAttr(out, "icon", si.getIcon()); // We don't save it. + ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); + ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras()); + ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight()); + ShortcutService.writeAttr(out, ATTR_TIMESTAMP, + si.getLastChangedTimestamp()); + ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags()); + ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId()); + ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); + + ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS, + si.getIntentPersistableExtras()); + ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); + + out.endTag(null, TAG_SHORTCUT); + } + + public static ShortcutPackage loadFromXml(XmlPullParser parser, int userId) + throws IOException, XmlPullParserException { + + final String packageName = ShortcutService.parseStringAttribute(parser, + ATTR_NAME); + + final ShortcutPackage ret = new ShortcutPackage(userId, packageName); + + ret.mDynamicShortcutCount = + ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT); + ret.mApiCallCount = + ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); + ret.mLastResetTime = + ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); + + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + switch (tag) { + case TAG_SHORTCUT: + final ShortcutInfo si = parseShortcut(parser, packageName); + + // Don't use addShortcut(), we don't need to save the icon. + ret.mShortcuts.put(si.getId(), si); + continue; + } + throw ShortcutService.throwForInvalidTag(depth, tag); + } + return ret; + } + + private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName) + throws IOException, XmlPullParserException { + String id; + ComponentName activityComponent; + // Icon icon; + String title; + Intent intent; + PersistableBundle intentPersistableExtras = null; + int weight; + PersistableBundle extras = null; + long lastChangedTimestamp; + int flags; + int iconRes; + String bitmapPath; + + id = ShortcutService.parseStringAttribute(parser, ATTR_ID); + activityComponent = ShortcutService.parseComponentNameAttribute(parser, + ATTR_ACTIVITY); + title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); + intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT); + weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT); + lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser, + ATTR_TIMESTAMP); + flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS); + iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES); + bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH); + + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + if (ShortcutService.DEBUG_LOAD) { + Slog.d(TAG, String.format(" depth=%d type=%d name=%s", + depth, type, tag)); + } + switch (tag) { + case TAG_INTENT_EXTRAS: + intentPersistableExtras = PersistableBundle.restoreFromXml(parser); + continue; + case TAG_EXTRAS: + extras = PersistableBundle.restoreFromXml(parser); + continue; + } + throw ShortcutService.throwForInvalidTag(depth, tag); + } + return new ShortcutInfo( + id, packageName, activityComponent, /* icon =*/ null, title, intent, + intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, + iconRes, bitmapPath); + } +} diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 64e76f0e7fbba..42954f528a2bf 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -222,7 +222,7 @@ public class ShortcutService extends IShortcutService.Stub { * User ID -> UserShortcuts */ @GuardedBy("mLock") - private final SparseArray mUsers = new SparseArray<>(); + private final SparseArray mUsers = new SparseArray<>(); /** * Max number of dynamic shortcuts that each application can have at a time. @@ -633,7 +633,7 @@ public class ShortcutService extends IShortcutService.Stub { } @Nullable - private UserShortcuts loadUserLocked(@UserIdInt int userId) { + private ShortcutUser loadUserLocked(@UserIdInt int userId) { final File path = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); if (DEBUG) { Slog.d(TAG, "Loading from " + path); @@ -649,7 +649,7 @@ public class ShortcutService extends IShortcutService.Stub { } return null; } - UserShortcuts ret = null; + ShortcutUser ret = null; try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, StandardCharsets.UTF_8.name()); @@ -666,8 +666,8 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("depth=%d type=%d name=%s", depth, type, tag)); } - if ((depth == 1) && UserShortcuts.TAG_ROOT.equals(tag)) { - ret = UserShortcuts.loadFromXml(parser, userId); + if ((depth == 1) && ShortcutUser.TAG_ROOT.equals(tag)) { + ret = ShortcutUser.loadFromXml(parser, userId); continue; } throwForInvalidTag(depth, tag); @@ -779,12 +779,12 @@ public class ShortcutService extends IShortcutService.Stub { /** Return the per-user state. */ @GuardedBy("mLock") @NonNull - UserShortcuts getUserShortcutsLocked(@UserIdInt int userId) { - UserShortcuts userPackages = mUsers.get(userId); + ShortcutUser getUserShortcutsLocked(@UserIdInt int userId) { + ShortcutUser userPackages = mUsers.get(userId); if (userPackages == null) { userPackages = loadUserLocked(userId); if (userPackages == null) { - userPackages = new UserShortcuts(userId); + userPackages = new ShortcutUser(userId); } mUsers.put(userId, userPackages); } @@ -794,14 +794,14 @@ public class ShortcutService extends IShortcutService.Stub { /** Return the per-user per-package state. */ @GuardedBy("mLock") @NonNull - PackageShortcuts getPackageShortcutsLocked( + ShortcutPackage getPackageShortcutsLocked( @NonNull String packageName, @UserIdInt int userId) { return getUserShortcutsLocked(userId).getPackageShortcuts(packageName); } @GuardedBy("mLock") @NonNull - LauncherShortcuts getLauncherShortcuts( + ShortcutLauncher getLauncherShortcuts( @NonNull String packageName, @UserIdInt int userId) { return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName); } @@ -1169,7 +1169,7 @@ public class ShortcutService extends IShortcutService.Stub { final int size = newShortcuts.size(); synchronized (mLock) { - final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId); + final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); // Throttling. if (!ps.tryApiCall(this)) { @@ -1204,7 +1204,7 @@ public class ShortcutService extends IShortcutService.Stub { final int size = newShortcuts.size(); synchronized (mLock) { - final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId); + final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); // Throttling. if (!ps.tryApiCall(this)) { @@ -1241,7 +1241,7 @@ public class ShortcutService extends IShortcutService.Stub { verifyCaller(packageName, userId); synchronized (mLock) { - final PackageShortcuts ps = getPackageShortcutsLocked(packageName, userId); + final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); // Throttling. if (!ps.tryApiCall(this)) { @@ -1381,7 +1381,7 @@ public class ShortcutService extends IShortcutService.Stub { start = System.currentTimeMillis(); } - final UserShortcuts user = getUserShortcutsLocked(userId); + final ShortcutUser user = getUserShortcutsLocked(userId); final List allHomeCandidates = new ArrayList<>(); @@ -1453,7 +1453,7 @@ public class ShortcutService extends IShortcutService.Stub { void cleanUpPackageLocked(String packageName, int userId) { final boolean wasUserLoaded = isUserLoadedLocked(userId); - final UserShortcuts mUser = getUserShortcutsLocked(userId); + final ShortcutUser mUser = getUserShortcutsLocked(userId); boolean doNotify = false; // First, remove the package from the package list (if the package is a publisher). @@ -1506,7 +1506,7 @@ public class ShortcutService extends IShortcutService.Stub { callingPackage, packageName, changedSince, componentName, queryFlags, userId, ret, cloneFlag); } else { - final ArrayMap packages = + final ArrayMap packages = getUserShortcutsLocked(userId).getPackages(); for (int i = packages.size() - 1; i >= 0; i--) { getShortcutsInnerLocked( @@ -1932,25 +1932,29 @@ public class ShortcutService extends IShortcutService.Stub { // === Unit test support === // Injection point. + @VisibleForTesting long injectCurrentTimeMillis() { return System.currentTimeMillis(); } // Injection point. + @VisibleForTesting int injectBinderCallingUid() { return getCallingUid(); } - final int getCallingUserId() { + private int getCallingUserId() { return UserHandle.getUserId(injectBinderCallingUid()); } // Injection point. + @VisibleForTesting long injectClearCallingIdentity() { return Binder.clearCallingIdentity(); } // Injection point. + @VisibleForTesting void injectRestoreCallingIdentity(long token) { Binder.restoreCallingIdentity(token); } @@ -1963,10 +1967,12 @@ public class ShortcutService extends IShortcutService.Stub { Slog.wtf(TAG, message, e); } + @VisibleForTesting File injectSystemDataPath() { return Environment.getDataSystemDirectory(); } + @VisibleForTesting File injectUserDataPath(@UserIdInt int userId) { return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); } @@ -1976,16 +1982,18 @@ public class ShortcutService extends IShortcutService.Stub { return ActivityManager.isLowRamDeviceStatic(); } + @VisibleForTesting PackageManagerInternal injectPackageManagerInternal() { return mPackageManagerInternal; } + @VisibleForTesting File getUserBitmapFilePath(@UserIdInt int userId) { return new File(injectUserDataPath(userId), DIRECTORY_BITMAPS); } @VisibleForTesting - SparseArray getShortcutsForTest() { + SparseArray getShortcutsForTest() { return mUsers; } @@ -2022,809 +2030,13 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { synchronized (mLock) { - final UserShortcuts user = mUsers.get(userId); + final ShortcutUser user = mUsers.get(userId); if (user == null) return null; - final PackageShortcuts pkg = user.getPackages().get(packageName); + final ShortcutPackage pkg = user.getPackages().get(packageName); if (pkg == null) return null; return pkg.findShortcutById(shortcutId); } } } - -/** - * Per-user information. - */ -class UserShortcuts { - private static final String TAG = ShortcutService.TAG; - - static final String TAG_ROOT = "user"; - private static final String TAG_LAUNCHER = "launcher"; - - private static final String ATTR_VALUE = "value"; - - @UserIdInt - final int mUserId; - - private final ArrayMap mPackages = new ArrayMap<>(); - - private final ArrayMap mLaunchers = new ArrayMap<>(); - - private ComponentName mLauncherComponent; - - public UserShortcuts(int userId) { - mUserId = userId; - } - - public ArrayMap getPackages() { - return mPackages; - } - - public ArrayMap getLaunchers() { - return mLaunchers; - } - - public PackageShortcuts getPackageShortcuts(@NonNull String packageName) { - PackageShortcuts ret = mPackages.get(packageName); - if (ret == null) { - ret = new PackageShortcuts(mUserId, packageName); - mPackages.put(packageName, ret); - } - return ret; - } - - public LauncherShortcuts getLauncherShortcuts(@NonNull String packageName) { - LauncherShortcuts ret = mLaunchers.get(packageName); - if (ret == null) { - ret = new LauncherShortcuts(mUserId, packageName); - mLaunchers.put(packageName, ret); - } - return ret; - } - - public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { - out.startTag(null, TAG_ROOT); - - ShortcutService.writeTagValue(out, TAG_LAUNCHER, - mLauncherComponent); - - final int lsize = mLaunchers.size(); - for (int i = 0; i < lsize; i++) { - mLaunchers.valueAt(i).saveToXml(out); - } - - final int psize = mPackages.size(); - for (int i = 0; i < psize; i++) { - mPackages.valueAt(i).saveToXml(out); - } - - out.endTag(null, TAG_ROOT); - } - - public static UserShortcuts loadFromXml(XmlPullParser parser, int userId) - throws IOException, XmlPullParserException { - final UserShortcuts ret = new UserShortcuts(userId); - - final int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final int depth = parser.getDepth(); - final String tag = parser.getName(); - switch (tag) { - case TAG_LAUNCHER: { - ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute( - parser, ATTR_VALUE); - continue; - } - case PackageShortcuts.TAG_ROOT: { - final PackageShortcuts shortcuts = PackageShortcuts.loadFromXml(parser, userId); - - // Don't use addShortcut(), we don't need to save the icon. - ret.getPackages().put(shortcuts.mPackageName, shortcuts); - continue; - } - - case LauncherShortcuts.TAG_ROOT: { - final LauncherShortcuts shortcuts = - LauncherShortcuts.loadFromXml(parser, userId); - - ret.getLaunchers().put(shortcuts.mPackageName, shortcuts); - continue; - } - } - throw ShortcutService.throwForInvalidTag(depth, tag); - } - return ret; - } - - public ComponentName getLauncherComponent() { - return mLauncherComponent; - } - - public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) { - if (Objects.equal(mLauncherComponent, launcherComponent)) { - return; - } - mLauncherComponent = launcherComponent; - s.scheduleSaveUser(mUserId); - } - - public void resetThrottling() { - for (int i = mPackages.size() - 1; i >= 0; i--) { - mPackages.valueAt(i).resetThrottling(); - } - } - - public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) { - pw.print(prefix); - pw.print("User: "); - pw.print(mUserId); - pw.println(); - - pw.print(prefix); - pw.print(" "); - pw.print("Default launcher: "); - pw.print(mLauncherComponent); - pw.println(); - - for (int i = 0; i < mLaunchers.size(); i++) { - mLaunchers.valueAt(i).dump(s, pw, prefix + " "); - } - - for (int i = 0; i < mPackages.size(); i++) { - mPackages.valueAt(i).dump(s, pw, prefix + " "); - } - } -} - -class LauncherShortcuts { - private static final String TAG = ShortcutService.TAG; - - static final String TAG_ROOT = "launcher-pins"; - - private static final String TAG_PACKAGE = "package"; - private static final String TAG_PIN = "pin"; - - private static final String ATTR_VALUE = "value"; - private static final String ATTR_PACKAGE_NAME = "package-name"; - - @UserIdInt - final int mUserId; - - @NonNull - final String mPackageName; - - /** - * Package name -> IDs. - */ - final private ArrayMap> mPinnedShortcuts = new ArrayMap<>(); - - LauncherShortcuts(@UserIdInt int userId, @NonNull String packageName) { - mUserId = userId; - mPackageName = packageName; - } - - public void pinShortcuts(@NonNull ShortcutService s, @NonNull String packageName, - @NonNull List ids) { - final int idSize = ids.size(); - if (idSize == 0) { - mPinnedShortcuts.remove(packageName); - } else { - final ArraySet prevSet = mPinnedShortcuts.get(packageName); - - // Pin shortcuts. Make sure only pin the ones that were visible to the caller. - // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here. - - final PackageShortcuts packageShortcuts = - s.getPackageShortcutsLocked(packageName, mUserId); - final ArraySet newSet = new ArraySet<>(); - - for (int i = 0; i < idSize; i++) { - final String id = ids.get(i); - final ShortcutInfo si = packageShortcuts.findShortcutById(id); - if (si == null) { - continue; - } - if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) { - newSet.add(id); - } - } - mPinnedShortcuts.put(packageName, newSet); - } - s.getPackageShortcutsLocked(packageName, mUserId).refreshPinnedFlags(s); - } - - /** - * Return the pinned shortcut IDs for the publisher package. - */ - public ArraySet getPinnedShortcutIds(@NonNull String packageName) { - return mPinnedShortcuts.get(packageName); - } - - boolean cleanUpPackage(String packageName) { - return mPinnedShortcuts.remove(packageName) != null; - } - - /** - * Persist. - */ - public void saveToXml(XmlSerializer out) throws IOException { - final int size = mPinnedShortcuts.size(); - if (size == 0) { - return; // Nothing to write. - } - - out.startTag(null, TAG_ROOT); - ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, - mPackageName); - - for (int i = 0; i < size; i++) { - out.startTag(null, TAG_PACKAGE); - ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, - mPinnedShortcuts.keyAt(i)); - - final ArraySet ids = mPinnedShortcuts.valueAt(i); - final int idSize = ids.size(); - for (int j = 0; j < idSize; j++) { - ShortcutService.writeTagValue(out, TAG_PIN, ids.valueAt(j)); - } - out.endTag(null, TAG_PACKAGE); - } - - out.endTag(null, TAG_ROOT); - } - - /** - * Load. - */ - public static LauncherShortcuts loadFromXml(XmlPullParser parser, int userId) - throws IOException, XmlPullParserException { - final String launcherPackageName = ShortcutService.parseStringAttribute(parser, - ATTR_PACKAGE_NAME); - - final LauncherShortcuts ret = new LauncherShortcuts(userId, launcherPackageName); - - ArraySet ids = null; - final int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final int depth = parser.getDepth(); - final String tag = parser.getName(); - switch (tag) { - case TAG_PACKAGE: { - final String packageName = ShortcutService.parseStringAttribute(parser, - ATTR_PACKAGE_NAME); - ids = new ArraySet<>(); - ret.mPinnedShortcuts.put(packageName, ids); - continue; - } - case TAG_PIN: { - ids.add(ShortcutService.parseStringAttribute(parser, - ATTR_VALUE)); - continue; - } - } - throw ShortcutService.throwForInvalidTag(depth, tag); - } - return ret; - } - - public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) { - pw.println(); - - pw.print(prefix); - pw.print("Launcher: "); - pw.print(mPackageName); - pw.println(); - - final int size = mPinnedShortcuts.size(); - for (int i = 0; i < size; i++) { - pw.println(); - - pw.print(prefix); - pw.print(" "); - pw.print("Package: "); - pw.println(mPinnedShortcuts.keyAt(i)); - - final ArraySet ids = mPinnedShortcuts.valueAt(i); - final int idSize = ids.size(); - - for (int j = 0; j < idSize; j++) { - pw.print(prefix); - pw.print(" "); - pw.print(ids.valueAt(j)); - pw.println(); - } - } - } -} - -/** - * All the information relevant to shortcuts from a single package (per-user). - */ -class PackageShortcuts { - private static final String TAG = ShortcutService.TAG; - - static final String TAG_ROOT = "package"; - private static final String TAG_INTENT_EXTRAS = "intent-extras"; - private static final String TAG_EXTRAS = "extras"; - private static final String TAG_SHORTCUT = "shortcut"; - - private static final String ATTR_NAME = "name"; - private static final String ATTR_DYNAMIC_COUNT = "dynamic-count"; - private static final String ATTR_CALL_COUNT = "call-count"; - private static final String ATTR_LAST_RESET = "last-reset"; - private static final String ATTR_ID = "id"; - private static final String ATTR_ACTIVITY = "activity"; - private static final String ATTR_TITLE = "title"; - private static final String ATTR_INTENT = "intent"; - private static final String ATTR_WEIGHT = "weight"; - private static final String ATTR_TIMESTAMP = "timestamp"; - private static final String ATTR_FLAGS = "flags"; - private static final String ATTR_ICON_RES = "icon-res"; - private static final String ATTR_BITMAP_PATH = "bitmap-path"; - - @UserIdInt - final int mUserId; - - @NonNull - final String mPackageName; - - /** - * All the shortcuts from the package, keyed on IDs. - */ - final private ArrayMap mShortcuts = new ArrayMap<>(); - - /** - * # of dynamic shortcuts. - */ - private int mDynamicShortcutCount = 0; - - /** - * # of times the package has called rate-limited APIs. - */ - private int mApiCallCount; - - /** - * When {@link #mApiCallCount} was reset last time. - */ - private long mLastResetTime; - - PackageShortcuts(int userId, String packageName) { - mUserId = userId; - mPackageName = packageName; - } - - @Nullable - public ShortcutInfo findShortcutById(String id) { - return mShortcuts.get(id); - } - - private ShortcutInfo deleteShortcut(@NonNull ShortcutService s, - @NonNull String id) { - final ShortcutInfo shortcut = mShortcuts.remove(id); - if (shortcut != null) { - s.removeIcon(mUserId, shortcut); - shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED); - } - return shortcut; - } - - void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) { - deleteShortcut(s, newShortcut.getId()); - s.saveIconAndFixUpShortcut(mUserId, newShortcut); - mShortcuts.put(newShortcut.getId(), newShortcut); - } - - /** - * Add a shortcut, or update one with the same ID, with taking over existing flags. - * - * It checks the max number of dynamic shortcuts. - */ - public void addDynamicShortcut(@NonNull ShortcutService s, - @NonNull ShortcutInfo newShortcut) { - newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); - - final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId()); - - final boolean wasPinned; - final int newDynamicCount; - - if (oldShortcut == null) { - wasPinned = false; - newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut. - } else { - wasPinned = oldShortcut.isPinned(); - if (oldShortcut.isDynamic()) { - newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut. - } else { - newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut. - } - } - - // Make sure there's still room. - s.enforceMaxDynamicShortcuts(newDynamicCount); - - // Okay, make it dynamic and add. - if (wasPinned) { - newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); - } - - addShortcut(s, newShortcut); - mDynamicShortcutCount = newDynamicCount; - } - - /** - * Remove all shortcuts that aren't pinned nor dynamic. - */ - private void removeOrphans(@NonNull ShortcutService s) { - ArrayList removeList = null; // Lazily initialize. - - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - final ShortcutInfo si = mShortcuts.valueAt(i); - - if (si.isPinned() || si.isDynamic()) continue; - - if (removeList == null) { - removeList = new ArrayList<>(); - } - removeList.add(si.getId()); - } - if (removeList != null) { - for (int i = removeList.size() - 1; i >= 0; i--) { - deleteShortcut(s, removeList.get(i)); - } - } - } - - /** - * Remove all dynamic shortcuts. - */ - public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) { - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC); - } - removeOrphans(s); - mDynamicShortcutCount = 0; - } - - /** - * Remove a dynamic shortcut by ID. - */ - public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) { - final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId); - - if (oldShortcut == null) { - return; - } - if (oldShortcut.isDynamic()) { - mDynamicShortcutCount--; - } - if (oldShortcut.isPinned()) { - oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC); - } else { - deleteShortcut(s, shortcutId); - } - } - - /** - * Called after a launcher updates the pinned set. For each shortcut in this package, - * set FLAG_PINNED if any launcher has pinned it. Otherwise, clear it. - * - *

Then remove all shortcuts that are not dynamic and no longer pinned either. - */ - public void refreshPinnedFlags(@NonNull ShortcutService s) { - // First, un-pin all shortcuts - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED); - } - - // Then, for the pinned set for each launcher, set the pin flag one by one. - final ArrayMap launchers = - s.getUserShortcutsLocked(mUserId).getLaunchers(); - - for (int l = launchers.size() - 1; l >= 0; l--) { - final LauncherShortcuts launcherShortcuts = launchers.valueAt(l); - final ArraySet pinned = launcherShortcuts.getPinnedShortcutIds(mPackageName); - - if (pinned == null || pinned.size() == 0) { - continue; - } - for (int i = pinned.size() - 1; i >= 0; i--) { - final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i)); - if (si == null) { - s.wtf("Shortcut not found"); - } else { - si.addFlags(ShortcutInfo.FLAG_PINNED); - } - } - } - - // Lastly, remove the ones that are no longer pinned nor dynamic. - removeOrphans(s); - } - - /** - * Number of calls that the caller has made, since the last reset. - */ - public int getApiCallCount(@NonNull ShortcutService s) { - final long last = s.getLastResetTimeLocked(); - - final long now = s.injectCurrentTimeMillis(); - if (ShortcutService.isClockValid(now) && mLastResetTime > now) { - Slog.w(TAG, "Clock rewound"); - // Clock rewound. - mLastResetTime = now; - mApiCallCount = 0; - return mApiCallCount; - } - - // If not reset yet, then reset. - if (mLastResetTime < last) { - if (ShortcutService.DEBUG) { - Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting", - mLastResetTime, now, last)); - } - mApiCallCount = 0; - mLastResetTime = last; - } - return mApiCallCount; - } - - /** - * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount} - * and return true. Otherwise just return false. - */ - public boolean tryApiCall(@NonNull ShortcutService s) { - if (getApiCallCount(s) >= s.mMaxDailyUpdates) { - return false; - } - mApiCallCount++; - return true; - } - - public void resetRateLimitingForCommandLine() { - mApiCallCount = 0; - mLastResetTime = 0; - } - - /** - * Find all shortcuts that match {@code query}. - */ - public void findAll(@NonNull ShortcutService s, @NonNull List result, - @Nullable Predicate query, int cloneFlag, - @Nullable String callingLauncher) { - - // Set of pinned shortcuts by the calling launcher. - final ArraySet pinnedByCallerSet = (callingLauncher == null) ? null - : s.getLauncherShortcuts(callingLauncher, mUserId) - .getPinnedShortcutIds(mPackageName); - - for (int i = 0; i < mShortcuts.size(); i++) { - final ShortcutInfo si = mShortcuts.valueAt(i); - - // If it's called by non-launcher (i.e. publisher, always include -> true. - // Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned - // it. - final boolean isPinnedByCaller = (callingLauncher == null) - || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId())); - if (!si.isDynamic()) { - if (!si.isPinned()) { - s.wtf("Shortcut not pinned here"); - continue; - } - if (!isPinnedByCaller) { - continue; - } - } - final ShortcutInfo clone = si.clone(cloneFlag); - // Fix up isPinned for the caller. Note we need to do it before the "test" callback, - // since it may check isPinned. - if (!isPinnedByCaller) { - clone.clearFlags(ShortcutInfo.FLAG_PINNED); - } - if (query == null || query.test(clone)) { - result.add(clone); - } - } - } - - public void resetThrottling() { - mApiCallCount = 0; - } - - public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) { - pw.println(); - - pw.print(prefix); - pw.print("Package: "); - pw.print(mPackageName); - pw.println(); - - pw.print(prefix); - pw.print(" "); - pw.print("Calls: "); - pw.print(getApiCallCount(s)); - pw.println(); - - // This should be after getApiCallCount(), which may update it. - pw.print(prefix); - pw.print(" "); - pw.print("Last reset: ["); - pw.print(mLastResetTime); - pw.print("] "); - pw.print(s.formatTime(mLastResetTime)); - pw.println(); - - pw.println(" Shortcuts:"); - long totalBitmapSize = 0; - final ArrayMap shortcuts = mShortcuts; - final int size = shortcuts.size(); - for (int i = 0; i < size; i++) { - final ShortcutInfo si = shortcuts.valueAt(i); - pw.print(" "); - pw.println(si.toInsecureString()); - if (si.getBitmapPath() != null) { - final long len = new File(si.getBitmapPath()).length(); - pw.print(" "); - pw.print("bitmap size="); - pw.println(len); - - totalBitmapSize += len; - } - } - pw.print(prefix); - pw.print(" "); - pw.print("Total bitmap size: "); - pw.print(totalBitmapSize); - pw.print(" ("); - pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize)); - pw.println(")"); - } - - public void saveToXml(@NonNull XmlSerializer out) throws IOException, XmlPullParserException { - final int size = mShortcuts.size(); - - if (size == 0 && mApiCallCount == 0) { - return; // nothing to write. - } - - out.startTag(null, TAG_ROOT); - - ShortcutService.writeAttr(out, ATTR_NAME, mPackageName); - ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount); - ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); - ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); - - for (int j = 0; j < size; j++) { - saveShortcut(out, mShortcuts.valueAt(j)); - } - - out.endTag(null, TAG_ROOT); - } - - private static void saveShortcut(XmlSerializer out, ShortcutInfo si) - throws IOException, XmlPullParserException { - out.startTag(null, TAG_SHORTCUT); - ShortcutService.writeAttr(out, ATTR_ID, si.getId()); - // writeAttr(out, "package", si.getPackageName()); // not needed - ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent()); - // writeAttr(out, "icon", si.getIcon()); // We don't save it. - ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); - ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras()); - ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight()); - ShortcutService.writeAttr(out, ATTR_TIMESTAMP, - si.getLastChangedTimestamp()); - ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags()); - ShortcutService.writeAttr(out, ATTR_ICON_RES, si.getIconResourceId()); - ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath()); - - ShortcutService.writeTagExtra(out, TAG_INTENT_EXTRAS, - si.getIntentPersistableExtras()); - ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras()); - - out.endTag(null, TAG_SHORTCUT); - } - - public static PackageShortcuts loadFromXml(XmlPullParser parser, int userId) - throws IOException, XmlPullParserException { - - final String packageName = ShortcutService.parseStringAttribute(parser, - ATTR_NAME); - - final PackageShortcuts ret = new PackageShortcuts(userId, packageName); - - ret.mDynamicShortcutCount = - ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT); - ret.mApiCallCount = - ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); - ret.mLastResetTime = - ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); - - final int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final int depth = parser.getDepth(); - final String tag = parser.getName(); - switch (tag) { - case TAG_SHORTCUT: - final ShortcutInfo si = parseShortcut(parser, packageName); - - // Don't use addShortcut(), we don't need to save the icon. - ret.mShortcuts.put(si.getId(), si); - continue; - } - throw ShortcutService.throwForInvalidTag(depth, tag); - } - return ret; - } - - private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName) - throws IOException, XmlPullParserException { - String id; - ComponentName activityComponent; - // Icon icon; - String title; - Intent intent; - PersistableBundle intentPersistableExtras = null; - int weight; - PersistableBundle extras = null; - long lastChangedTimestamp; - int flags; - int iconRes; - String bitmapPath; - - id = ShortcutService.parseStringAttribute(parser, ATTR_ID); - activityComponent = ShortcutService.parseComponentNameAttribute(parser, - ATTR_ACTIVITY); - title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); - intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT); - weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT); - lastChangedTimestamp = (int) ShortcutService.parseLongAttribute(parser, - ATTR_TIMESTAMP); - flags = (int) ShortcutService.parseLongAttribute(parser, ATTR_FLAGS); - iconRes = (int) ShortcutService.parseLongAttribute(parser, ATTR_ICON_RES); - bitmapPath = ShortcutService.parseStringAttribute(parser, ATTR_BITMAP_PATH); - - final int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final int depth = parser.getDepth(); - final String tag = parser.getName(); - if (ShortcutService.DEBUG_LOAD) { - Slog.d(TAG, String.format(" depth=%d type=%d name=%s", - depth, type, tag)); - } - switch (tag) { - case TAG_INTENT_EXTRAS: - intentPersistableExtras = PersistableBundle.restoreFromXml(parser); - continue; - case TAG_EXTRAS: - extras = PersistableBundle.restoreFromXml(parser); - continue; - } - throw ShortcutService.throwForInvalidTag(depth, tag); - } - return new ShortcutInfo( - id, packageName, activityComponent, /* icon =*/ null, title, intent, - intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, - iconRes, bitmapPath); - } -} diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java new file mode 100644 index 0000000000000..4a6b1e4d6bfc5 --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.pm; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.util.ArrayMap; + +import libcore.util.Objects; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.PrintWriter; + +/** + * User information used by {@link ShortcutService}. + */ +class ShortcutUser { + private static final String TAG = ShortcutService.TAG; + + static final String TAG_ROOT = "user"; + private static final String TAG_LAUNCHER = "launcher"; + + private static final String ATTR_VALUE = "value"; + + @UserIdInt + final int mUserId; + + private final ArrayMap mPackages = new ArrayMap<>(); + + private final ArrayMap mLaunchers = new ArrayMap<>(); + + private ComponentName mLauncherComponent; + + public ShortcutUser(int userId) { + mUserId = userId; + } + + public ArrayMap getPackages() { + return mPackages; + } + + public ArrayMap getLaunchers() { + return mLaunchers; + } + + public ShortcutPackage getPackageShortcuts(@NonNull String packageName) { + ShortcutPackage ret = mPackages.get(packageName); + if (ret == null) { + ret = new ShortcutPackage(mUserId, packageName); + mPackages.put(packageName, ret); + } + return ret; + } + + public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName) { + ShortcutLauncher ret = mLaunchers.get(packageName); + if (ret == null) { + ret = new ShortcutLauncher(mUserId, packageName); + mLaunchers.put(packageName, ret); + } + return ret; + } + + public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException { + out.startTag(null, TAG_ROOT); + + ShortcutService.writeTagValue(out, TAG_LAUNCHER, + mLauncherComponent); + + final int lsize = mLaunchers.size(); + for (int i = 0; i < lsize; i++) { + mLaunchers.valueAt(i).saveToXml(out); + } + + final int psize = mPackages.size(); + for (int i = 0; i < psize; i++) { + mPackages.valueAt(i).saveToXml(out); + } + + out.endTag(null, TAG_ROOT); + } + + public static ShortcutUser loadFromXml(XmlPullParser parser, int userId) + throws IOException, XmlPullParserException { + final ShortcutUser ret = new ShortcutUser(userId); + + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + switch (tag) { + case TAG_LAUNCHER: { + ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute( + parser, ATTR_VALUE); + continue; + } + case ShortcutPackage.TAG_ROOT: { + final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml(parser, userId); + + // Don't use addShortcut(), we don't need to save the icon. + ret.getPackages().put(shortcuts.mPackageName, shortcuts); + continue; + } + + case ShortcutLauncher.TAG_ROOT: { + final ShortcutLauncher shortcuts = + ShortcutLauncher.loadFromXml(parser, userId); + + ret.getLaunchers().put(shortcuts.mPackageName, shortcuts); + continue; + } + } + throw ShortcutService.throwForInvalidTag(depth, tag); + } + return ret; + } + + public ComponentName getLauncherComponent() { + return mLauncherComponent; + } + + public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) { + if (Objects.equal(mLauncherComponent, launcherComponent)) { + return; + } + mLauncherComponent = launcherComponent; + s.scheduleSaveUser(mUserId); + } + + public void resetThrottling() { + for (int i = mPackages.size() - 1; i >= 0; i--) { + mPackages.valueAt(i).resetThrottling(); + } + } + + public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) { + pw.print(prefix); + pw.print("User: "); + pw.print(mUserId); + pw.println(); + + pw.print(prefix); + pw.print(" "); + pw.print("Default launcher: "); + pw.print(mLauncherComponent); + pw.println(); + + for (int i = 0; i < mLaunchers.size(); i++) { + mLaunchers.valueAt(i).dump(s, pw, prefix + " "); + } + + for (int i = 0; i < mPackages.size(); i++) { + mPackages.valueAt(i).dump(s, pw, prefix + " "); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index 68199b42ed69c..28966ca5a7227 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -2605,13 +2605,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { }); - final SparseArray users = mService.getShortcutsForTest(); + final SparseArray users = mService.getShortcutsForTest(); assertEquals(2, users.size()); assertEquals(USER_0, users.keyAt(0)); assertEquals(USER_10, users.keyAt(1)); - final UserShortcuts user0 = users.get(USER_0); - final UserShortcuts user10 = users.get(USER_10); + final ShortcutUser user0 = users.get(USER_0); + final ShortcutUser user10 = users.get(USER_10); // Check the registered packages.