Merge "Limit the number of shortcuts per app that can be retained by system" into tm-dev

This commit is contained in:
Pinyao Ting
2023-03-04 02:41:01 +00:00
committed by Android (Google) Code Review
2 changed files with 112 additions and 12 deletions

View File

@@ -430,6 +430,7 @@ class ShortcutPackage extends ShortcutPackageItem {
@NonNull List<ShortcutInfo> changedShortcuts) {
Preconditions.checkArgument(newShortcut.isEnabled(),
"pushDynamicShortcuts() cannot publish disabled shortcuts");
ensureShortcutCountBeforePush();
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
@@ -437,7 +438,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final ShortcutInfo oldShortcut = findShortcutById(newShortcut.getId());
boolean deleted = false;
if (oldShortcut == null) {
if (oldShortcut == null || !oldShortcut.isDynamic()) {
final ShortcutService service = mShortcutUser.mService;
final int maxShortcuts = service.getMaxActivityShortcuts();
@@ -446,18 +447,12 @@ class ShortcutPackage extends ShortcutPackageItem {
final ArrayList<ShortcutInfo> activityShortcuts = all.get(newShortcut.getActivity());
if (activityShortcuts != null && activityShortcuts.size() > maxShortcuts) {
Slog.e(TAG, "Error pushing shortcut. There are already "
+ activityShortcuts.size() + " shortcuts, exceeding the " + maxShortcuts
+ " shortcuts limit when pushing the new shortcut " + newShortcut
+ ". Id of shortcuts currently available in system memory are "
+ activityShortcuts.stream().map(ShortcutInfo::getId)
.collect(Collectors.joining(",", "[", "]")));
// TODO: This should not have happened. If it does, identify the root cause where
// possible, otherwise bail-out early to prevent memory issue.
// Root cause was discovered in b/233155034, so this should not be happening.
service.wtf("Error pushing shortcut. There are already "
+ activityShortcuts.size() + " shortcuts.");
}
if (activityShortcuts != null && activityShortcuts.size() == maxShortcuts) {
// Max has reached. Delete the shortcut with lowest rank.
// Sort by isManifestShortcut() and getRank().
Collections.sort(activityShortcuts, mShortcutTypeAndRankComparator);
@@ -473,7 +468,8 @@ class ShortcutPackage extends ShortcutPackageItem {
deleted = deleteDynamicWithId(shortcut.getId(), /* ignoreInvisible =*/ true,
/*ignorePersistedShortcuts=*/ true) != null;
}
} else {
}
if (oldShortcut != null) {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
@@ -505,6 +501,32 @@ class ShortcutPackage extends ShortcutPackageItem {
return deleted;
}
private void ensureShortcutCountBeforePush() {
final ShortcutService service = mShortcutUser.mService;
// Ensure the total number of shortcuts doesn't exceed the hard limit per app.
final int maxShortcutPerApp = service.getMaxAppShortcuts();
synchronized (mLock) {
final List<ShortcutInfo> appShortcuts = mShortcuts.values().stream().filter(si ->
!si.isPinned()).collect(Collectors.toList());
if (appShortcuts.size() >= maxShortcutPerApp) {
// Max has reached. Removes shortcuts until they fall within the hard cap.
// Sort by isManifestShortcut(), isDynamic() and getLastChangedTimestamp().
Collections.sort(appShortcuts, mShortcutTypeRankAndTimeComparator);
while (appShortcuts.size() >= maxShortcutPerApp) {
final ShortcutInfo shortcut = appShortcuts.remove(appShortcuts.size() - 1);
if (shortcut.isDeclaredInManifest()) {
// All shortcuts are manifest shortcuts and cannot be removed.
throw new IllegalArgumentException(getPackageName() + " has published "
+ appShortcuts.size() + " manifest shortcuts across different"
+ " activities.");
}
forceDeleteShortcutInner(shortcut.getId());
}
}
}
}
/**
* Remove all shortcuts that aren't pinned, cached nor dynamic.
*
@@ -1366,6 +1388,61 @@ class ShortcutPackage extends ShortcutPackageItem {
return Integer.compare(a.getRank(), b.getRank());
};
/**
* To sort by isManifestShortcut(), isDynamic(), getRank() and
* getLastChangedTimestamp(). i.e. manifest shortcuts come before non-manifest shortcuts,
* dynamic shortcuts come before floating shortcuts, then sort by last changed timestamp.
*
* This is used to decide which shortcuts to remove when the total number of shortcuts retained
* for the app exceeds the limit defined in {@link ShortcutService#getMaxAppShortcuts()}.
*
* (Note the number of manifest shortcuts is always <= the max number, because if there are
* more, ShortcutParser would ignore the rest.)
*/
final Comparator<ShortcutInfo> mShortcutTypeRankAndTimeComparator = (ShortcutInfo a,
ShortcutInfo b) -> {
if (a.isDeclaredInManifest() && !b.isDeclaredInManifest()) {
return -1;
}
if (!a.isDeclaredInManifest() && b.isDeclaredInManifest()) {
return 1;
}
if (a.isDynamic() && b.isDynamic()) {
return Integer.compare(a.getRank(), b.getRank());
}
if (a.isDynamic()) {
return -1;
}
if (b.isDynamic()) {
return 1;
}
if (a.isCached() && b.isCached()) {
// if both shortcuts are cached, prioritize shortcuts cached by people tile,
if (a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)
&& !b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) {
return -1;
} else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)
&& b.hasFlags(ShortcutInfo.FLAG_CACHED_PEOPLE_TILE)) {
return 1;
}
// followed by bubbles.
if (a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)
&& !b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) {
return -1;
} else if (!a.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)
&& b.hasFlags(ShortcutInfo.FLAG_CACHED_BUBBLES)) {
return 1;
}
}
if (a.isCached()) {
return -1;
}
if (b.isCached()) {
return 1;
}
return Long.compare(b.getLastChangedTimestamp(), a.getLastChangedTimestamp());
};
/**
* Build a list of shortcuts for each target activity and return as a map. The result won't
* contain "floating" shortcuts because they don't belong on any activities.

View File

@@ -180,6 +180,9 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
@VisibleForTesting
static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 100;
@VisibleForTesting
static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
@@ -256,6 +259,11 @@ public class ShortcutService extends IShortcutService.Stub {
*/
String KEY_MAX_SHORTCUTS = "max_shortcuts";
/**
* Key name for the max shortcuts can be retained in system ram per app. (int)
*/
String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
/**
* Key name for icon compression quality, 0-100.
*/
@@ -328,10 +336,15 @@ public class ShortcutService extends IShortcutService.Stub {
new SparseArray<>();
/**
* Max number of dynamic + manifest shortcuts that each application can have at a time.
* Max number of dynamic + manifest shortcuts that each activity can have at a time.
*/
private int mMaxShortcuts;
/**
* Max number of shortcuts that can exists in system ram for each application.
*/
private int mMaxShortcutsPerApp;
/**
* Max number of updating API calls that each application can make during the interval.
*/
@@ -806,6 +819,9 @@ public class ShortcutService extends IShortcutService.Stub {
mMaxShortcuts = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
? (int) parser.getLong(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1757,6 +1773,13 @@ public class ShortcutService extends IShortcutService.Stub {
return mMaxShortcuts;
}
/**
* Return the max number of shortcuts can be retaiend in system ram for each application.
*/
int getMaxAppShortcuts() {
return mMaxShortcutsPerApp;
}
/**
* - Sends a notification to LauncherApps
* - Write to file