Merge \"ShortcutManger: Auto-adjust ranks.\" into nyc-mr1-dev

am: 70a91541e9

Change-Id: I42d2d2a954bd2f52bd7b75cfd051bab0def100da
This commit is contained in:
Makoto Onuki
2016-06-15 21:59:09 +00:00
committed by android-build-merger
10 changed files with 1125 additions and 110 deletions

View File

@@ -62,6 +62,13 @@ public final class ShortcutInfo implements Parcelable {
private static final String ANDROID_PACKAGE_NAME = "android";
private static final int IMPLICIT_RANK_MASK = 0x7fffffff;
private static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK;
/** @hide */
public static final int RANK_NOT_SET = Integer.MAX_VALUE;
/** @hide */
public static final int FLAG_DYNAMIC = 1 << 0;
@@ -193,6 +200,15 @@ public final class ShortcutInfo implements Parcelable {
private int mRank;
/**
* Internally used for auto-rank-adjustment.
*
* RANK_CHANGED_BIT is used to denote that the rank of a shortcut is changing.
* The rest of the bits are used to denote the order in which shortcuts are passed to
* APIs, which is used to preserve the argument order when ranks are tie.
*/
private int mImplicitRank;
@Nullable
private PersistableBundle mExtras;
@@ -544,7 +560,8 @@ public final class ShortcutInfo implements Parcelable {
/**
* Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information
* will be overwritten. The timestamp will be updated.
* will be overwritten. The timestamp will *not* be updated to be consistent with other
* setters (and also the clock is not injectable in this file).
*
* - Flags will not change
* - mBitmapPath will not change
@@ -603,14 +620,12 @@ public final class ShortcutInfo implements Parcelable {
mIntent = source.mIntent;
mIntentPersistableExtras = source.mIntentPersistableExtras;
}
if (source.mRank != 0) {
if (source.mRank != RANK_NOT_SET) {
mRank = source.mRank;
}
if (source.mExtras != null) {
mExtras = source.mExtras;
}
updateTimestamp();
}
/**
@@ -665,7 +680,7 @@ public final class ShortcutInfo implements Parcelable {
private Intent mIntent;
private int mRank;
private int mRank = RANK_NOT_SET;
private PersistableBundle mExtras;
@@ -825,15 +840,18 @@ public final class ShortcutInfo implements Parcelable {
@NonNull
public Builder setIntent(@NonNull Intent intent) {
mIntent = Preconditions.checkNotNull(intent, "intent");
Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set.");
Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set");
return this;
}
/**
* TODO javadoc.
* "Rank" of a shortcut, which is a non-negative value that's used by the launcher app
* to sort shortcuts.
*/
@NonNull
public Builder setRank(int rank) {
Preconditions.checkArgument((0 <= rank),
"Rank cannot be negative or bigger than MAX_RANK");
mRank = rank;
return this;
}
@@ -1014,12 +1032,57 @@ public final class ShortcutInfo implements Parcelable {
}
/**
* TODO Javadoc
* "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each
* {@link #getActivity} for each of the two kinds, dynamic shortcuts and manifest shortcuts.
*
* <p>Because manifest shortcuts and dynamic shortcuts have overlapping ranks,
* when a launcher application shows shortcuts for an activity, it should first show
* the manifest shortcuts followed by the dynamic shortcuts. Within each of those categories,
* shortcuts should be sorted by rank in ascending order.
*
* <p>"Floating" shortcuts (i.e. shortcuts that are neither dynamic nor manifest) will all
* have rank 0, because there's no sorting for them.
*/
public int getRank() {
return mRank;
}
/** @hide */
public boolean hasRank() {
return mRank != RANK_NOT_SET;
}
/** @hide */
public void setRank(int rank) {
mRank = rank;
}
/** @hide */
public void clearImplicitRankAndRankChangedFlag() {
mImplicitRank = 0;
}
/** @hide */
public void setImplicitRank(int rank) {
// Make sure to keep RANK_CHANGED_BIT.
mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK);
}
/** @hide */
public int getImplicitRank() {
return mImplicitRank & IMPLICIT_RANK_MASK;
}
/** @hide */
public void setRankChanged() {
mImplicitRank |= RANK_CHANGED_BIT;
}
/** @hide */
public boolean isRankChanged() {
return (mImplicitRank & RANK_CHANGED_BIT) != 0;
}
/**
* Optional values that application can set.
*/

View File

@@ -19,10 +19,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.os.PersistableBundle;
@@ -51,8 +49,6 @@ import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import sun.misc.Resource;
/**
* Package information used by {@link ShortcutService}.
* User information used by {@link ShortcutService}.
@@ -63,6 +59,7 @@ import sun.misc.Resource;
*/
class ShortcutPackage extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
private static final String TAG_VERIFY = ShortcutService.TAG + ".verify";
static final String TAG_ROOT = "package";
private static final String TAG_INTENT_EXTRAS = "intent-extras";
@@ -303,12 +300,17 @@ class ShortcutPackage extends ShortcutPackageItem {
* Remove all dynamic shortcuts.
*/
public void deleteAllDynamicShortcuts() {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isDynamic()) {
changed = true;
si.setTimestamp(now);
si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
si.setRank(0); // It may still be pinned, so clear the rank.
}
}
if (changed) {
@@ -356,10 +358,14 @@ class ShortcutPackage extends ShortcutPackageItem {
ensureNotImmutable(oldShortcut);
}
if (oldShortcut.isPinned()) {
oldShortcut.setRank(0);
oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
if (disable) {
oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
}
oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
return oldShortcut;
} else {
deleteShortcutInner(shortcutId);
@@ -829,7 +835,7 @@ class ShortcutPackage extends ShortcutPackageItem {
if (!a.isManifestShortcut() && b.isManifestShortcut()) {
return 1;
}
return a.getRank() - b.getRank();
return Integer.compare(a.getRank(), b.getRank());
};
/**
@@ -837,8 +843,6 @@ class ShortcutPackage extends ShortcutPackageItem {
* contain "floating" shortcuts because they don't belong on any activities.
*/
private ArrayMap<ComponentName, ArrayList<ShortcutInfo>> sortShortcutsToActivities() {
final int maxShortcuts = mShortcutUser.mService.getMaxActivityShortcuts();
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> activitiesToShortcuts
= new ArrayMap<>();
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
@@ -851,7 +855,7 @@ class ShortcutPackage extends ShortcutPackageItem {
ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
if (list == null) {
list = new ArrayList<>(maxShortcuts * 2);
list = new ArrayList<>();
activitiesToShortcuts.put(activity, list);
}
list.add(si);
@@ -976,6 +980,96 @@ class ShortcutPackage extends ShortcutPackageItem {
}
}
/** Clears the implicit ranks for all shortcuts. */
public void clearAllImplicitRanks() {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
si.clearImplicitRankAndRankChangedFlag();
}
}
/**
* Used to sort shortcuts for rank auto-adjusting.
*/
final Comparator<ShortcutInfo> mShortcutRankComparator = (ShortcutInfo a, ShortcutInfo b) -> {
// First, sort by rank.
int ret = Integer.compare(a.getRank(), b.getRank());
if (ret != 0) {
return ret;
}
// When ranks are tie, then prioritize the ones that have just been assigned new ranks.
// e.g. when there are 3 shortcuts, "s1" "s2" and "s3" with rank 0, 1, 2 respectively,
// adding a shortcut "s4" with rank 1 will "insert" it between "s1" and "s2", because
// "s2" and "s4" have the same rank 1 but s4 has isRankChanged() set.
// Similarly, updating s3's rank to 1 will insert it between s1 and s2.
if (a.isRankChanged() != b.isRankChanged()) {
return a.isRankChanged() ? -1 : 1;
}
// If they're still tie, sort by implicit rank -- i.e. preserve the order in which
// they're passed to the API.
ret = Integer.compare(a.getImplicitRank(), b.getImplicitRank());
if (ret != 0) {
return ret;
}
// If they're stil tie, just sort by their IDs.
// This may happen with updateShortcuts() -- see
// the testUpdateShortcuts_noManifestShortcuts() test.
return a.getId().compareTo(b.getId());
};
/**
* Re-calculate the ranks for all shortcuts.
*/
public void adjustRanks() {
final ShortcutService s = mShortcutUser.mService;
final long now = s.injectCurrentTimeMillis();
// First, clear ranks for floating shortcuts.
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isFloating()) {
if (si.getRank() != 0) {
si.setTimestamp(now);
si.setRank(0);
}
}
}
// Then adjust ranks. Ranks are unique for each activity, so we first need to sort
// shortcuts to each activity.
// Then sort the shortcuts within each activity with mShortcutRankComparator, and
// assign ranks from 0.
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
sortShortcutsToActivities();
for (int outer = all.size() - 1; outer >= 0; outer--) { // For each activity.
final ArrayList<ShortcutInfo> list = all.valueAt(outer);
// Sort by ranks and other signals.
Collections.sort(list, mShortcutRankComparator);
int rank = 0;
final int size = list.size();
for (int i = 0; i < size; i++) {
final ShortcutInfo si = list.get(i);
if (si.isManifestShortcut()) {
// Don't adjust ranks for manifest shortcuts.
continue;
}
// At this point, it must be dynamic.
if (!si.isDynamic()) {
s.wtf("Non-dynamic shortcut found.");
continue;
}
final int thisRank = rank++;
if (si.getRank() != thisRank) {
si.setTimestamp(now);
si.setRank(thisRank);
}
}
}
}
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println();
@@ -1087,7 +1181,6 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
si.getDisabledMessageResName());
ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras());
ShortcutService.writeAttr(out, ATTR_RANK, si.getRank());
ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
si.getLastChangedTimestamp());
if (forBackup) {
@@ -1097,6 +1190,10 @@ class ShortcutPackage extends ShortcutPackageItem {
~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
| ShortcutInfo.FLAG_DYNAMIC));
} else {
// When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
// as dynamic.
ShortcutService.writeAttr(out, ATTR_RANK, si.getRank());
ShortcutService.writeAttr(out, ATTR_FLAGS, si.getFlags());
ShortcutService.writeAttr(out, ATTR_ICON_RES_ID, si.getIconResourceId());
ShortcutService.writeAttr(out, ATTR_ICON_RES_NAME, si.getIconResName());
@@ -1272,35 +1369,74 @@ class ShortcutPackage extends ShortcutPackageItem {
sortShortcutsToActivities();
// Make sure each activity won't have more than max shortcuts.
for (int i = all.size() - 1; i >= 0; i--) {
if (all.valueAt(i).size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
for (int outer = all.size() - 1; outer >= 0; outer--) {
final ArrayList<ShortcutInfo> list = all.valueAt(outer);
if (list.size() > mShortcutUser.mService.getMaxActivityShortcuts()) {
failed = true;
Log.e(TAG, "Package " + getPackageName() + ": activity " + all.keyAt(i)
+ " has " + all.valueAt(i).size() + " shortcuts.");
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": activity " + all.keyAt(outer)
+ " has " + all.valueAt(outer).size() + " shortcuts.");
}
// Sort by rank.
Collections.sort(list, (a, b) -> Integer.compare(a.getRank(), b.getRank()));
// Split into two arrays for each kind.
final ArrayList<ShortcutInfo> dynamicList = new ArrayList<>(list);
dynamicList.removeIf((si) -> !si.isDynamic());
final ArrayList<ShortcutInfo> manifestList = new ArrayList<>(list);
dynamicList.removeIf((si) -> !si.isManifestShortcut());
verifyRanksSequential(dynamicList);
verifyRanksSequential(manifestList);
}
// Verify each shortcut's status.
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (!(si.isManifestShortcut() || si.isDynamic() || si.isPinned())) {
failed = true;
Log.e(TAG, "Package " + getPackageName() + ": shortcut " + si.getId()
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not manifest, dynamic or pinned.");
}
if (si.isManifestShortcut() && si.isDynamic()) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is both dynamic and manifest at the same time.");
}
if (si.getActivity() == null) {
failed = true;
Log.e(TAG, "Package " + getPackageName() + ": shortcut " + si.getId()
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " has null activity.");
}
if ((si.isDynamic() || si.isManifestShortcut()) && !si.isEnabled()) {
failed = true;
Log.e(TAG, "Package " + getPackageName() + ": shortcut " + si.getId()
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is not floating, but is disabled.");
}
if (si.isFloating() && si.getRank() != 0) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " is floating, but has rank=" + si.getRank());
}
}
if (failed) {
throw new IllegalStateException("See logcat for errors");
}
}
private boolean verifyRanksSequential(List<ShortcutInfo> list) {
boolean failed = false;
for (int i = 0; i < list.size(); i++) {
final ShortcutInfo si = list.get(i);
if (si.getRank() != i) {
failed = true;
Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+ " rank=" + si.getRank() + " but expected to be "+ i);
}
}
return failed;
}
}

View File

@@ -103,7 +103,7 @@ public class ShortcutParser {
}
if (depth == 2 && TAG_SHORTCUT.equals(tag)) {
final ShortcutInfo si = parseShortcutAttributes(
service, attrs, packageName, activity, userId, rank++);
service, attrs, packageName, activity, userId, rank);
if (ShortcutService.DEBUG) {
Slog.d(TAG, "Shortcut=" + si);
}
@@ -128,6 +128,7 @@ public class ShortcutParser {
}
result.add(si);
numShortcuts++;
rank++;
}
continue;
}

View File

@@ -114,7 +114,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -223,7 +222,7 @@ public class ShortcutService extends IShortcutService.Stub {
String KEY_MAX_ICON_DIMENSION_DP_LOWRAM = "max_icon_dimension_dp_lowram";
/**
* Key name for the max dynamic shortcuts per app. (int)
* Key name for the max dynamic shortcuts per activity. (int)
*/
String KEY_MAX_SHORTCUTS = "max_shortcuts";
@@ -1479,6 +1478,12 @@ public class ShortcutService extends IShortcutService.Stub {
return (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
}
private void assignImplicitRanks(List<ShortcutInfo> shortcuts) {
for (int i = shortcuts.size() - 1; i >= 0; i--) {
shortcuts.get(i).setImplicitRank(i);
}
}
// === APIs ===
@Override
@@ -1501,10 +1506,9 @@ public class ShortcutService extends IShortcutService.Stub {
return false;
}
// Validate the shortcuts.
for (int i = 0; i < size; i++) {
fixUpIncomingShortcutInfo(newShortcuts.get(i), /* forUpdate= */ false);
}
// Initialize the implicit ranks for ShortcutPackage.adjustRanks().
ps.clearAllImplicitRanks();
assignImplicitRanks(newShortcuts);
// First, remove all un-pinned; dynamic shortcuts
ps.deleteAllDynamicShortcuts();
@@ -1514,6 +1518,9 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutInfo newShortcut = newShortcuts.get(i);
ps.addOrUpdateDynamicShortcut(newShortcut);
}
// Lastly, adjust the ranks.
ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -1542,41 +1549,58 @@ public class ShortcutService extends IShortcutService.Stub {
return false;
}
// Initialize the implicit ranks for ShortcutPackage.adjustRanks().
ps.clearAllImplicitRanks();
assignImplicitRanks(newShortcuts);
for (int i = 0; i < size; i++) {
final ShortcutInfo source = newShortcuts.get(i);
fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
final ShortcutInfo target = ps.findShortcutById(source.getId());
if (target != null) {
if (target.isEnabled() != source.isEnabled()) {
Slog.w(TAG,
"ShortcutInfo.enabled cannot be changed with updateShortcuts()");
}
if (target == null) {
continue;
}
final boolean replacingIcon = (source.getIcon() != null);
if (replacingIcon) {
removeIcon(userId, target);
}
if (target.isEnabled() != source.isEnabled()) {
Slog.w(TAG,
"ShortcutInfo.enabled cannot be changed with updateShortcuts()");
}
if (source.getActivity() != null &&
!source.getActivity().equals(target.getActivity())) {
// TODO When activity is changing, check the dynamic count.
}
// When updating the rank, we need to insert between existing ranks, so set
// this setRankChanged, and also copy the implicit rank fo adjustRanks().
if (source.hasRank()) {
target.setRankChanged();
target.setImplicitRank(source.getImplicitRank());
}
// Note copyNonNullFieldsFrom() does the "updatable with?" check too.
target.copyNonNullFieldsFrom(source);
final boolean replacingIcon = (source.getIcon() != null);
if (replacingIcon) {
removeIcon(userId, target);
}
if (replacingIcon) {
saveIconAndFixUpShortcut(userId, target);
}
if (source.getActivity() != null &&
!source.getActivity().equals(target.getActivity())) {
// TODO When activity is changing, check the dynamic count.
}
// When we're updating any resource related fields, re-extract the res names and
// the values.
if (replacingIcon || source.hasStringResources()) {
fixUpShortcutResourceNamesAndValues(target);
}
// Note copyNonNullFieldsFrom() does the "updatable with?" check too.
target.copyNonNullFieldsFrom(source);
target.setTimestamp(injectCurrentTimeMillis());
if (replacingIcon) {
saveIconAndFixUpShortcut(userId, target);
}
// When we're updating any resource related fields, re-extract the res names and
// the values.
if (replacingIcon || source.hasStringResources()) {
fixUpShortcutResourceNamesAndValues(target);
}
}
// Lastly, adjust the ranks.
ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -1600,6 +1624,10 @@ public class ShortcutService extends IShortcutService.Stub {
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
// Initialize the implicit ranks for ShortcutPackage.adjustRanks().
ps.clearAllImplicitRanks();
assignImplicitRanks(newShortcuts);
// Throttling.
if (!ps.tryApiCall()) {
return false;
@@ -1610,9 +1638,16 @@ public class ShortcutService extends IShortcutService.Stub {
// Validate the shortcut.
fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
// When ranks are changing, we need to insert between ranks, so set the
// "rank changed" flag.
newShortcut.setRankChanged();
// Add it.
ps.addOrUpdateDynamicShortcut(newShortcut);
}
// Lastly, adjust the ranks.
ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -1637,6 +1672,9 @@ public class ShortcutService extends IShortcutService.Stub {
disabledMessage, disabledMessageResId,
/* overrideImmutable=*/ false);
}
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -1677,6 +1715,9 @@ public class ShortcutService extends IShortcutService.Stub {
ps.deleteDynamicWithId(
Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
}
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
ps.adjustRanks();
}
packageShortcutsChanged(packageName, userId);
@@ -2328,6 +2369,7 @@ public class ShortcutService extends IShortcutService.Stub {
} finally {
logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
}
verifyStates();
}
private void handlePackageAdded(String packageName, @UserIdInt int userId) {
@@ -2339,6 +2381,7 @@ public class ShortcutService extends IShortcutService.Stub {
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
user.handlePackageAddedOrUpdated(packageName);
}
verifyStates();
}
private void handlePackageUpdateFinished(String packageName, @UserIdInt int userId) {
@@ -2354,6 +2397,7 @@ public class ShortcutService extends IShortcutService.Stub {
user.handlePackageAddedOrUpdated(packageName);
}
}
verifyStates();
}
private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
@@ -2362,6 +2406,8 @@ public class ShortcutService extends IShortcutService.Stub {
packageUserId));
}
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
verifyStates();
}
private void handlePackageDataCleared(String packageName, int packageUserId) {
@@ -2370,6 +2416,8 @@ public class ShortcutService extends IShortcutService.Stub {
packageUserId));
}
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
verifyStates();
}
// === PackageManager interaction ===

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="ms5"
android:enabled="true"
android:shortcutIcon="@drawable/icon1"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutLongLabel="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
android:shortcutIntentAction="action1"
android:shortcutIntentData="http://a.b.c/1"
/>
<shortcut
android:shortcutId="ms4"
android:enabled="true"
android:shortcutIcon="@drawable/icon2"
android:shortcutShortLabel="@string/shortcut_title2"
android:shortcutLongLabel="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
android:shortcutCategories="android.shortcut.conversation"
android:shortcutIntentAction="action2"
/>
<shortcut
android:shortcutId="ms3"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
<shortcut
android:shortcutId="ms2"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
<shortcut
android:shortcutId="ms1"
android:shortcutShortLabel="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
</shortcuts>

View File

@@ -742,6 +742,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
if (mService != null) {
// Flush all the unsaved data from the previous instance.
mService.saveDirtyInfo();
// Make sure everything is consistent.
mService.verifyStates();
}
LocalServices.removeServiceForTest(ShortcutServiceInternal.class);

View File

@@ -45,6 +45,7 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.filterByActivity;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.findShortcut;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.hashSet;
@@ -71,7 +72,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
@@ -79,7 +79,6 @@ import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
@@ -969,23 +968,34 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Set up shortcuts.
setCaller(CALLING_PACKAGE_1);
final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 5000);
final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 1000);
final ShortcutInfo s1_1 = makeShortcut("s1");
final ShortcutInfo s1_2 = makeShortcut("s2");
assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
// Because setDynamicShortcuts will update the timestamps when ranks are changing,
// we explicitly set timestamps here.
getCallerShortcut("s1").setTimestamp(5000);
getCallerShortcut("s2").setTimestamp(1000);
setCaller(CALLING_PACKAGE_2);
final ShortcutInfo s2_2 = makeShortcutWithTimestamp("s2", 1500);
final ShortcutInfo s2_3 = makeShortcutWithTimestampWithActivity("s3", 3000,
final ShortcutInfo s2_2 = makeShortcut("s2");
final ShortcutInfo s2_3 = makeShortcutWithActivity("s3",
makeComponent(ShortcutActivity2.class));
final ShortcutInfo s2_4 = makeShortcutWithTimestampWithActivity("s4", 500,
final ShortcutInfo s2_4 = makeShortcutWithActivity("s4",
makeComponent(ShortcutActivity.class));
assertTrue(mManager.setDynamicShortcuts(list(s2_2, s2_3, s2_4)));
getCallerShortcut("s2").setTimestamp(1500);
getCallerShortcut("s3").setTimestamp(3000);
getCallerShortcut("s4").setTimestamp(500);
setCaller(CALLING_PACKAGE_3);
final ShortcutInfo s3_2 = makeShortcutWithTimestamp("s3", START_TIME + 5000);
final ShortcutInfo s3_2 = makeShortcut("s3");
assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
setCaller(LAUNCHER_1);
// Get dynamic
@@ -4606,7 +4616,6 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertNull(mService.getPackageShortcutForTest(LAUNCHER_1, USER_0));
}
public void testManifestShortcut_publishOnBroadcast() {
// First, no packages are installed.
uninstallPackage(USER_0, CALLING_PACKAGE_1);
@@ -4677,6 +4686,10 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2", "ms3", "ms4", "ms5");
assertWith(getCallerShortcuts()).selectManifest()
.selectByActivity(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
.haveRanksInOrder("ms1", "ms2", "ms3", "ms4", "ms5");
assertEmpty(mManager.getPinnedShortcuts());
});
@@ -4710,6 +4723,10 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2");
assertWith(getCallerShortcuts()).selectManifest()
.selectByActivity(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
.haveRanksInOrder("ms1", "ms2");
assertShortcutIds(assertAllImmutable(assertAllPinned(
mManager.getPinnedShortcuts())),
"ms2", "ms3");
@@ -4733,6 +4750,10 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2");
assertWith(getCallerShortcuts()).selectManifest()
.selectByActivity(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
.haveRanksInOrder("ms1", "ms2");
assertEmpty(mManager.getPinnedShortcuts());
});
@@ -4741,6 +4762,10 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2");
assertWith(getCallerShortcuts()).selectManifest()
.selectByActivity(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
.haveRanksInOrder("ms1", "ms2");
assertShortcutIds(assertAllImmutable(assertAllPinned(
mManager.getPinnedShortcuts())),
"ms2", "ms3");
@@ -4749,10 +4774,43 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertAllDisabled(list(getCallerShortcut("ms3")));
});
// Multiple activities.
// Add shortcuts on activity 2 for package 2.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
R.xml.shortcut_5_alt);
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()),
R.xml.shortcut_5_reverse);
updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2", "ms3", "ms4", "ms5",
"ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
// Make sure they have the correct ranks, regardless of their ID's alphabetical order.
assertWith(getCallerShortcuts()).selectManifest()
.selectByActivity(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()))
.haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
assertWith(getCallerShortcuts()).selectManifest()
.selectByActivity(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()))
.haveRanksInOrder("ms5", "ms4", "ms3", "ms2", "ms1");
});
// Package 2 now has no manifest shortcuts.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
R.xml.shortcut_0);
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_2, ShortcutActivity2.class.getName()),
R.xml.shortcut_0);
updatePackageLastUpdateTime(CALLING_PACKAGE_2, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
@@ -5128,7 +5186,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// ShortcutActivity2 has two shortcuts, ms1 and ms2.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
R.xml.shortcut_2);
R.xml.shortcut_5);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
@@ -5136,13 +5194,14 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
mManager.getManifestShortcuts()))),
"ms1", "ms2");
"ms1", "ms2", "ms3", "ms4", "ms5");
// ms1 should belong to ShortcutActivity.
ShortcutInfo si = getCallerShortcut("ms1");
assertEquals(R.string.shortcut_title1, si.getTitleResId());
assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
si.getActivity());
assertEquals(0, si.getRank());
// ms2 should belong to ShortcutActivity*2*.
si = getCallerShortcut("ms2");
@@ -5150,8 +5209,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals(new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
si.getActivity());
// Also check the ranks
assertWith(getCallerShortcuts()).selectManifest()
.selectByActivity(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()))
.haveRanksInOrder("ms1");
assertWith(getCallerShortcuts()).selectManifest()
.selectByActivity(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()))
.haveRanksInOrder("ms2", "ms3", "ms4", "ms5");
// Make sure there's no other dangling shortcuts.
assertShortcutIds(getCallerShortcuts(), "ms1", "ms2");
assertShortcutIds(getCallerShortcuts(), "ms1", "ms2", "ms3", "ms4", "ms5");
});
}

View File

@@ -533,6 +533,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
.setActivity(new ComponentName("x", "y")).build());
assertEquals("text", si.getText());
assertEquals(123, si.getRank());
assertEquals(new ComponentName("x", "y"), si.getActivity());
si = sorig.clone(/* flags=*/ 0);
@@ -627,16 +628,6 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb2).build());
assertEquals("text", si.getText());
assertEquals(99, si.getExtras().getInt("x"));
// Make sure the timestamp gets updated too.
final long timestamp = si.getLastChangedTimestamp();
Thread.sleep(2);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
.setTitle("xyz").build());
assertTrue(si.getLastChangedTimestamp() > timestamp);
}
public void testShortcutInfoCopyNonNullFieldsFrom_resId() throws InterruptedException {
@@ -764,16 +755,6 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb2).build());
assertEquals(11, si.getTextResId());
assertEquals(99, si.getExtras().getInt("x"));
// Make sure the timestamp gets updated too.
final long timestamp = si.getLastChangedTimestamp();
Thread.sleep(2);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id")
.setTitle("xyz").build());
assertTrue(si.getLastChangedTimestamp() > timestamp);
}
public void testShortcutInfoSaveAndLoad() throws InterruptedException {
@@ -797,7 +778,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb)
.build();
mManager.addDynamicShortcuts(list(sorig));
ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
.setId("id2")
.setTitle("x")
.setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
.setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
.setRank(456)
.build();
mManager.addDynamicShortcuts(list(sorig, sorig2));
Thread.sleep(2);
final long now = System.currentTimeMillis();
@@ -822,7 +811,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
assertEquals(123, si.getRank());
assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE
@@ -830,6 +819,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertNotNull(si.getBitmapPath()); // Something should be set.
assertEquals(0, si.getIconResourceId());
assertTrue(si.getLastChangedTimestamp() < now);
// Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts
// to test it.
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
assertEquals(1, si.getRank());
}
public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
@@ -852,7 +846,15 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb)
.build();
mManager.addDynamicShortcuts(list(sorig));
ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
.setId("id2")
.setTitle("x")
.setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
.setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
.setRank(456)
.build();
mManager.addDynamicShortcuts(list(sorig, sorig2));
Thread.sleep(2);
final long now = System.currentTimeMillis();
@@ -880,7 +882,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
assertEquals(123, si.getRank());
assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_RES
@@ -888,6 +890,11 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertNull(si.getBitmapPath());
assertEquals(R.drawable.black_32x32, si.getIconResourceId());
assertTrue(si.getLastChangedTimestamp() < now);
// Make sure ranks are saved too. Because of the auto-adjusting, we need two shortcuts
// to test it.
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_10);
assertEquals(1, si.getRank());
}
public void testShortcutInfoSaveAndLoad_forBackup() {
@@ -911,11 +918,19 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb)
.build();
mManager.addDynamicShortcuts(list(sorig));
ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
.setId("id2")
.setTitle("x")
.setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
.setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
.setRank(456)
.build();
mManager.addDynamicShortcuts(list(sorig, sorig2));
// Dynamic shortcuts won't be backed up, so we need to pin it.
setCaller(LAUNCHER_1, USER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id"), HANDLE_USER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);
// Do backup & restore.
backupAndRestore();
@@ -935,12 +950,16 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
assertEquals(123, si.getRank());
assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
assertNull(si.getBitmapPath()); // No icon.
assertEquals(0, si.getIconResourceId());
// Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
assertEquals(0, si.getRank());
}
public void testShortcutInfoSaveAndLoad_forBackup_resId() {
@@ -963,11 +982,19 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
.setExtras(pb)
.build();
mManager.addDynamicShortcuts(list(sorig));
ShortcutInfo sorig2 = new ShortcutInfo.Builder(mClientContext)
.setId("id2")
.setTitle("x")
.setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
.setIntent(makeIntent("action", ShortcutActivity.class, "key", "val"))
.setRank(456)
.build();
mManager.addDynamicShortcuts(list(sorig, sorig2));
// Dynamic shortcuts won't be backed up, so we need to pin it.
setCaller(LAUNCHER_1, USER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id"), HANDLE_USER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("id", "id2"), HANDLE_USER_0);
// Do backup & restore.
backupAndRestore();
@@ -990,13 +1017,17 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
assertEquals(123, si.getRank());
assertEquals(0, si.getRank());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
assertNull(si.getBitmapPath()); // No icon.
assertEquals(0, si.getIconResourceId());
assertEquals(null, si.getIconResName());
// Note when restored from backup, it's no longer dynamic, so shouldn't have a rank.
si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id2", USER_0);
assertEquals(0, si.getRank());
}
public void testThrottling() {

View File

@@ -0,0 +1,505 @@
/*
* 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 static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
import android.content.ComponentName;
import android.content.pm.ShortcutInfo;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.frameworks.servicestests.R;
import com.android.server.pm.ShortcutService.ConfigConstants;
/**
* Tests related to shortcut rank auto-adjustment.
*/
@SmallTest
public class ShortcutManagerTest3 extends BaseShortcutManagerTest {
private static final String CALLING_PACKAGE = CALLING_PACKAGE_1;
private static final ComponentName A1 = new ComponentName(CALLING_PACKAGE,
ShortcutActivity.class.getName());
private static final ComponentName A2 = new ComponentName(CALLING_PACKAGE,
ShortcutActivity2.class.getName());
private static final ComponentName A3 = new ComponentName(CALLING_PACKAGE,
ShortcutActivity3.class.getName());
private ShortcutInfo shortcut(String id, ComponentName activity, int rank) {
return makeShortcutWithActivityAndRank(id, activity, rank);
}
private ShortcutInfo shortcut(String id, ComponentName activity) {
return makeShortcutWithActivityAndRank(id, activity, ShortcutInfo.RANK_NOT_SET);
}
@Override
protected void setUp() throws Exception {
super.setUp();
// We don't need throttling during this test class, and also relax the max cap.
mService.updateConfigurationLocked(
ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999,"
+ ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
);
setCaller(CALLING_PACKAGE, USER_0);
}
private void publishManifestShortcuts(ComponentName activity, int resId) {
addManifestShortcutResource(activity, resId);
updatePackageVersion(CALLING_PACKAGE, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE, USER_0));
}
public void testSetDynamicShortcuts_noManifestShortcuts() {
mManager.setDynamicShortcuts(list(
shortcut("s1", A1)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s1");
assertTrue(mManager.setDynamicShortcuts(list(
shortcut("s5", A1),
shortcut("s4", A1),
shortcut("s3", A1)
)));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s4", "s3");
// RANK_NOT_SET is always the last.
assertTrue(mManager.setDynamicShortcuts(list(
shortcut("s5", A1),
shortcut("s4", A1, 5),
shortcut("s3", A1, 3),
shortcut("s2", A1)
)));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s3", "s4", "s5", "s2");
// Same rank, preserve the argument order.
assertTrue(mManager.setDynamicShortcuts(list(
shortcut("s5", A1, 5),
shortcut("s4", A1, 0),
shortcut("s3", A1, 5)
)));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s4", "s5", "s3");
// Multiple activities.
assertTrue(mManager.setDynamicShortcuts(list(
shortcut("s5", A1),
shortcut("s4", A2),
shortcut("s3", A3)
)));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A3)
.haveRanksInOrder("s3");
assertTrue(mManager.setDynamicShortcuts(list(
shortcut("s5", A1, 5),
shortcut("s4", A1),
shortcut("s3", A1, 5),
shortcut("x5", A2, 5),
shortcut("x4", A2),
shortcut("x3", A2, 1)
)));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x5", "x4");
// Clear. Make sure it wouldn't lead to invalid internals state.
// (ShortcutService.verifyStates() will do so internally.)
assertTrue(mManager.setDynamicShortcuts(list()));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1).isEmpty();
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2).isEmpty();
}
private void runTestWithManifestShortcuts(Runnable r) {
publishManifestShortcuts(A1, R.xml.shortcut_5_alt);
publishManifestShortcuts(A2, R.xml.shortcut_1);
assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A1)
.haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A2)
.haveRanksInOrder("ms1");
// Existence of manifest shortcuts shouldn't affect dynamic shortcut ranks,
// so running another test here should pass.
r.run();
// And dynamic shortcut tests shouldn't affect manifest shortcuts, so repeat the
// same check.
assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A1)
.haveRanksInOrder("ms1_alt", "ms2_alt", "ms3_alt", "ms4_alt", "ms5_alt");
assertWith(getCallerShortcuts()).selectManifest().selectByActivity(A2)
.haveRanksInOrder("ms1");
}
public void testSetDynamicShortcuts_withManifestShortcuts() {
runTestWithManifestShortcuts(() -> testSetDynamicShortcuts_noManifestShortcuts());
}
public void testAddDynamicShortcuts_noManifestShortcuts() {
mManager.addDynamicShortcuts(list(
shortcut("s1", A1)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s1");
//------------------------------------------------------
long lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.addDynamicShortcuts(list(
shortcut("s5", A1, 0),
shortcut("s4", A1),
shortcut("s2", A1, 3),
shortcut("x1", A2),
shortcut("x3", A2, 2),
shortcut("x2", A2, 2),
shortcut("s3", A1, 0)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s1", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s5", "s3", "s1", "s2", "s4", "x3", "x2", "x1");
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.addDynamicShortcuts(list(
shortcut("s1", A1, 1)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s1", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s1", "s3");
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.addDynamicShortcuts(list(
shortcut("s1", A1, 1),
// This is add, not update, so the following means s5 will have NO_RANK,
// which puts it at the end.
shortcut("s5", A1),
shortcut("s3", A1, 0),
// s10 also has NO_RANK, so it'll be put at the end, even after "s5" as we preserve
// the argument order.
shortcut("s10", A1),
// Note we're changing the activity for x2.
shortcut("x2", A1, 0),
shortcut("x10", A2)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s3", "x2", "s1", "s2", "s4", "s5", "s10");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x1", "x10");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s3", "x2", "s1", "s5", "s10", "x1", "x10");
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
// Change the activities again.
mManager.addDynamicShortcuts(list(
shortcut("s1", A2),
shortcut("s2", A2, 999)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s3", "x2", "s4", "s5", "s10");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x1", "x10", "s2", "s1");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s1", "s2", "s4", "s5", "s10");
}
public void testAddDynamicShortcuts_withManifestShortcuts() {
runTestWithManifestShortcuts(() -> testAddDynamicShortcuts_noManifestShortcuts());
}
public void testUpdateShortcuts_noManifestShortcuts() {
mManager.addDynamicShortcuts(list(
shortcut("s5", A1, 0),
shortcut("s4", A1),
shortcut("s2", A1, 3),
shortcut("x1", A2),
shortcut("x3", A2, 2),
shortcut("x2", A2, 2),
shortcut("s3", A1, 0)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
//------------------------------------------------------
long lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.updateShortcuts(list());
// Same order.
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.isEmpty();
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE, list("s2", "s4", "x2"), HANDLE_USER_0);
});
// Still same order.
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.updateShortcuts(list(
shortcut("s4", A1, 1),
// Rank not changing, should keep the same positions.
// c.f. in case of addDynamicShortcuts, this means "put them at the end".
shortcut("s3", A1),
shortcut("x2", A2)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s4", "s3", "s2");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s4", "s3", "s2", "x2");
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.updateShortcuts(list(
shortcut("s4", A1, 0),
// Change the activity without specifying a rank -> keep the same rank.
shortcut("s5", A2),
// Change the activity without specifying a rank -> assign a new rank.
shortcut("x2", A1, 2),
// "xx" doesn't exist, so it'll be ignored.
shortcut("xx", A1, 0)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s4", "x2", "s3", "s2");
// Interesting case: both x3 and s5 originally had rank=0, and in this case s5 has moved
// to A2 without changing the rank. So they're tie for the new rank, as well as
// the "rank changed" bit. Also in this case, "s5" won't have an implicit order, since
// its rank isn't changing. So we sort them by ID, thus s5 comes before x3.
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("s5", "x3", "x1");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s4", "x2", "s5", "x3");
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.updateShortcuts(list(
shortcut("s3", A3)));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s4", "x2", "s2");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("s5", "x3", "x1");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A3)
.haveRanksInOrder("s3");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s3", "s2");
}
public void testUpdateShortcuts_withManifestShortcuts() {
runTestWithManifestShortcuts(() -> testUpdateShortcuts_noManifestShortcuts());
}
public void testDeleteDynamicShortcuts_noManifestShortcuts() {
mManager.addDynamicShortcuts(list(
shortcut("s5", A1, 0),
shortcut("s4", A1),
shortcut("s2", A1, 3),
shortcut("x1", A2),
shortcut("x3", A2, 2),
shortcut("x2", A2, 2),
shortcut("s3", A1, 0)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
//------------------------------------------------------
long lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.removeDynamicShortcuts(list());
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.isEmpty();
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(
CALLING_PACKAGE, list("s2", "s4", "x1", "x2"), HANDLE_USER_0);
});
// Still same order.
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.removeDynamicShortcuts(list("s3", "x1", "xxxx"));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s2", "s4");
}
public void testDeleteDynamicShortcuts_withManifestShortcuts() {
runTestWithManifestShortcuts(() -> testDeleteDynamicShortcuts_noManifestShortcuts());
}
public void testDisableShortcuts_noManifestShortcuts() {
mManager.addDynamicShortcuts(list(
shortcut("s5", A1, 0),
shortcut("s4", A1),
shortcut("s2", A1, 3),
shortcut("x1", A2),
shortcut("x3", A2, 2),
shortcut("x2", A2, 2),
shortcut("s3", A1, 0)
));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
//------------------------------------------------------
long lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.disableShortcuts(list());
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s3", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2", "x1");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.isEmpty();
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.disableShortcuts(list("s3", "x1", "xxxx"));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s2", "s4");
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE, list("s2", "s4", "x2"), HANDLE_USER_0);
});
// Still same order.
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s2", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x3", "x2");
//------------------------------------------------------
lastApiTime = ++mInjectedCurrentTimeMillis;
mManager.disableShortcuts(list("s2", "x3"));
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A1)
.haveRanksInOrder("s5", "s4");
assertWith(getCallerShortcuts()).selectDynamic().selectByActivity(A2)
.haveRanksInOrder("x2");
assertWith(getCallerShortcuts()).selectDynamic().selectByChangedSince(lastApiTime)
.haveIds("s4", "x2");
}
public void testDisableShortcuts_withManifestShortcuts() {
runTestWithManifestShortcuts(() -> testDisableShortcuts_noManifestShortcuts());
}
}

View File

@@ -58,9 +58,13 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -72,10 +76,12 @@ import java.util.function.Predicate;
public class ShortcutManagerTestUtils {
private static final String TAG = "ShortcutManagerUtils";
private static final boolean ENABLE_DUMPSYS = true; // DO NOT SUBMIT WITH true
private static final boolean ENABLE_DUMPSYS = false; // DO NOT SUBMIT WITH true
private static final int STANDARD_TIMEOUT_SEC = 5;
private static final String[] EMPTY_STRINGS = new String[0];
private ShortcutManagerTestUtils() {
}
@@ -228,7 +234,7 @@ public class ShortcutManagerTestUtils {
}
public static <T> Set<T> hashSet(Set<T> in) {
return new HashSet<T>(in);
return new LinkedHashSet<>(in);
}
public static <T> Set<T> set(T... values) {
@@ -240,7 +246,7 @@ public class ShortcutManagerTestUtils {
}
public static <T, V> Set<T> set(Function<V, T> converter, List<V> values) {
final HashSet<T> ret = new HashSet<>();
final LinkedHashSet<T> ret = new LinkedHashSet<>();
for (V v : values) {
ret.add(converter.apply(v));
}
@@ -258,15 +264,21 @@ public class ShortcutManagerTestUtils {
return list;
}
public static List<ShortcutInfo> filter(List<ShortcutInfo> list, Predicate<ShortcutInfo> p) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>(list);
ret.removeIf(si -> !p.test(si));
return ret;
}
public static List<ShortcutInfo> filterByActivity(List<ShortcutInfo> list,
ComponentName activity) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
for (ShortcutInfo si : list) {
if (si.getActivity().equals(activity) && (si.isManifestShortcut() || si.isDynamic())) {
ret.add(si);
}
}
return ret;
return filter(list, si ->
(si.getActivity().equals(activity)
&& (si.isManifestShortcut() || si.isDynamic())));
}
public static List<ShortcutInfo> changedSince(List<ShortcutInfo> list, long time) {
return filter(list, si -> si.getLastChangedTimestamp() >= time);
}
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
@@ -305,8 +317,8 @@ public class ShortcutManagerTestUtils {
public static List<ShortcutInfo> assertShortcutIds(List<ShortcutInfo> actualShortcuts,
String... expectedIds) {
final HashSet<String> expected = new HashSet<>(list(expectedIds));
final HashSet<String> actual = new HashSet<>();
final SortedSet<String> expected = new TreeSet<>(list(expectedIds));
final SortedSet<String> actual = new TreeSet<>();
for (ShortcutInfo s : actualShortcuts) {
actual.add(s.getId());
}
@@ -316,6 +328,17 @@ public class ShortcutManagerTestUtils {
return actualShortcuts;
}
public static List<ShortcutInfo> assertShortcutIdsOrdered(List<ShortcutInfo> actualShortcuts,
String... expectedIds) {
final ArrayList<String> expected = new ArrayList<>(list(expectedIds));
final ArrayList<String> actual = new ArrayList<>();
for (ShortcutInfo s : actualShortcuts) {
actual.add(s.getId());
}
assertEquals(expected, actual);
return actualShortcuts;
}
public static List<ShortcutInfo> assertAllHaveIntents(
List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
@@ -482,7 +505,7 @@ public class ShortcutManagerTestUtils {
}
public static <T> void assertAllUnique(Collection<T> list) {
final Set<Object> set = new HashSet<>();
final Set<Object> set = new LinkedHashSet<>();
for (T item : list) {
if (set.contains(item)) {
fail("Duplicate item found: " + item + " (in the list: " + list + ")");
@@ -594,6 +617,15 @@ public class ShortcutManagerTestUtils {
return ret;
}
private static final Comparator<ShortcutInfo> sRankComparator =
(ShortcutInfo a, ShortcutInfo b) -> Integer.compare(a.getRank(), b.getRank());
public static List<ShortcutInfo> sortedByRank(List<ShortcutInfo> shortcuts) {
final ArrayList<ShortcutInfo> ret = new ArrayList<>(shortcuts);
Collections.sort(ret, sRankComparator);
return ret;
}
public static void waitUntil(String message, BooleanSupplier condition) {
waitUntil(message, condition, STANDARD_TIMEOUT_SEC);
}
@@ -612,4 +644,78 @@ public class ShortcutManagerTestUtils {
}
fail("Timed out for: " + message);
}
public static ShortcutListAsserter assertWith(List<ShortcutInfo> list) {
return new ShortcutListAsserter(list);
}
/**
* New style assertion that allows chained calls.
*/
public static class ShortcutListAsserter {
private final List<ShortcutInfo> mList;
ShortcutListAsserter(List<ShortcutInfo> list) {
mList = new ArrayList<>(list);
}
public ShortcutListAsserter selectDynamic() {
return new ShortcutListAsserter(
filter(mList, ShortcutInfo::isDynamic));
}
public ShortcutListAsserter selectManifest() {
return new ShortcutListAsserter(
filter(mList, ShortcutInfo::isManifestShortcut));
}
public ShortcutListAsserter selectPinned() {
return new ShortcutListAsserter(
filter(mList, ShortcutInfo::isPinned));
}
public ShortcutListAsserter selectByActivity(ComponentName activity) {
return new ShortcutListAsserter(
ShortcutManagerTestUtils.filterByActivity(mList, activity));
}
public ShortcutListAsserter selectByChangedSince(long time) {
return new ShortcutListAsserter(
ShortcutManagerTestUtils.changedSince(mList, time));
}
public ShortcutListAsserter toSortByRank() {
return new ShortcutListAsserter(
ShortcutManagerTestUtils.sortedByRank(mList));
}
public ShortcutListAsserter haveIds(String... expectedIds) {
assertShortcutIds(mList, expectedIds);
return this;
}
public ShortcutListAsserter haveIdsOrdered(String... expectedIds) {
assertShortcutIdsOrdered(mList, expectedIds);
return this;
}
private ShortcutListAsserter haveSequentialRanks() {
for (int i = 0; i < mList.size(); i++) {
assertEquals("Rank not sequential", i, mList.get(i).getRank());
}
return this;
}
public ShortcutListAsserter haveRanksInOrder(String... expectedIds) {
toSortByRank()
.haveSequentialRanks()
.haveIdsOrdered(expectedIds);
return this;
}
public ShortcutListAsserter isEmpty() {
assertEquals(0, mList.size());
return this;
}
}
}