Merge "ShortcutManager: Implement max # of shortcuts" into nyc-mr1-dev
This commit is contained in:
@@ -26,11 +26,13 @@ import android.os.PersistableBundle;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
import com.android.server.pm.ShortcutService.ShortcutOperation;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
@@ -40,6 +42,8 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
@@ -126,7 +130,8 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a shortcut is about to be published. At this point we know the publisher package
|
||||
* Called when a shortcut is about to be published. At this point we know the publisher
|
||||
* package
|
||||
* exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
|
||||
* we do some initialization for the package.
|
||||
*/
|
||||
@@ -292,12 +297,17 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a dynamic shortcut by ID.
|
||||
* Remove a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
|
||||
* is pinned, it'll remain as a pinned shortcut, and is still enabled.
|
||||
*/
|
||||
public void deleteDynamicWithId(@NonNull String shortcutId) {
|
||||
deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
|
||||
* is pinned, it'll remain as a pinned shortcut but will be disabled.
|
||||
*/
|
||||
public void disableWithId(@NonNull String shortcutId, String disabledMessage,
|
||||
int disabledMessageResId, boolean overrideImmutable) {
|
||||
final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
|
||||
@@ -625,6 +635,10 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
// (Re-)publish manifest shortcut.
|
||||
changed |= publishManifestShortcuts(newManifestShortcutList);
|
||||
|
||||
if (newManifestShortcutList != null) {
|
||||
changed |= pushOutExcessShortcuts();
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
// This will send a notification to the launcher, and also save .
|
||||
s.packageShortcutsChanged(getPackageName(), getPackageUserId());
|
||||
@@ -642,10 +656,6 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
}
|
||||
boolean changed = false;
|
||||
|
||||
// TODO: Check dynamic count
|
||||
|
||||
// TODO: Kick out dynamic if too many
|
||||
|
||||
// Keep the previous IDs.
|
||||
ArraySet<String> toDisableList = null;
|
||||
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
|
||||
@@ -669,7 +679,7 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
final ShortcutInfo newShortcut = newManifestShortcutList.get(i);
|
||||
final boolean newDisabled = !newShortcut.isEnabled();
|
||||
|
||||
final String id = newShortcut.getId();
|
||||
final String id = newShortcut.getId();
|
||||
final ShortcutInfo oldShortcut = mShortcuts.get(id);
|
||||
|
||||
boolean wasPinned = false;
|
||||
@@ -692,7 +702,6 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
// Just keep it in toDisableList, so the previous one would be removed.
|
||||
continue;
|
||||
}
|
||||
// TODO: Check dynamic count
|
||||
|
||||
// Note even if enabled=false, we still need to update all fields, so do it
|
||||
// regardless.
|
||||
@@ -725,6 +734,179 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each target activity, make sure # of dynamic + manifest shortcuts <= max.
|
||||
* If too many, we'll remove the dynamic with the lowest ranks.
|
||||
*/
|
||||
private boolean pushOutExcessShortcuts() {
|
||||
final ShortcutService service = mShortcutUser.mService;
|
||||
final int maxShortcuts = service.getMaxActivityShortcuts();
|
||||
|
||||
boolean changed = false;
|
||||
|
||||
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
|
||||
sortShortcutsToActivities();
|
||||
for (int outer = all.size() - 1; outer >= 0; outer--) {
|
||||
final ArrayList<ShortcutInfo> list = all.valueAt(outer);
|
||||
if (list.size() <= maxShortcuts) {
|
||||
continue;
|
||||
}
|
||||
// Sort by isManifestShortcut() and getRank().
|
||||
Collections.sort(list, mShortcutTypeAndRankComparator);
|
||||
|
||||
// Keep [0 .. max), and remove (as dynamic) [max .. size)
|
||||
for (int inner = list.size() - 1; inner >= maxShortcuts; inner--) {
|
||||
final ShortcutInfo shortcut = list.get(inner);
|
||||
|
||||
if (shortcut.isManifestShortcut()) {
|
||||
// This shouldn't happen -- excess shortcuts should all be non-manifest.
|
||||
// But just in case.
|
||||
service.wtf("Found manifest shortcuts in excess list.");
|
||||
continue;
|
||||
}
|
||||
deleteDynamicWithId(shortcut.getId());
|
||||
}
|
||||
}
|
||||
service.verifyStates();
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* To sort by isManifestShortcut() and getRank(). i.e. manifest shortcuts come before
|
||||
* non-manifest shortcuts, then sort by rank.
|
||||
*
|
||||
* This is used to decide which dynamic shortcuts to remove when an upgraded version has more
|
||||
* manifest shortcuts than before and as a result we need to remove some of the dynamic
|
||||
* shortcuts. We sort manifest + dynamic shortcuts by this order, and remove the ones with
|
||||
* the last ones.
|
||||
*
|
||||
* (Note the number of manifest shortcuts is always <= the max number, because if there are
|
||||
* more, ShortcutParser would ignore the rest.)
|
||||
*/
|
||||
final Comparator<ShortcutInfo> mShortcutTypeAndRankComparator = (ShortcutInfo a,
|
||||
ShortcutInfo b) -> {
|
||||
if (a.isManifestShortcut() && !b.isManifestShortcut()) {
|
||||
return -1;
|
||||
}
|
||||
if (!a.isManifestShortcut() && b.isManifestShortcut()) {
|
||||
return 1;
|
||||
}
|
||||
return a.getRank() - b.getRank();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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--) {
|
||||
final ShortcutInfo si = mShortcuts.valueAt(i);
|
||||
if (si.isFloating()) {
|
||||
continue; // Ignore floating shortcuts, which are not tied to any activities.
|
||||
}
|
||||
|
||||
final ComponentName activity = si.getActivity();
|
||||
|
||||
ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>(maxShortcuts * 2);
|
||||
activitiesToShortcuts.put(activity, list);
|
||||
}
|
||||
list.add(si);
|
||||
}
|
||||
return activitiesToShortcuts;
|
||||
}
|
||||
|
||||
/** Used by {@link #enforceShortcutCountsBeforeOperation} */
|
||||
private void incrementCountForActivity(ArrayMap<ComponentName, Integer> counts,
|
||||
ComponentName cn, int increment) {
|
||||
Integer oldValue = counts.get(cn);
|
||||
if (oldValue == null) {
|
||||
oldValue = 0;
|
||||
}
|
||||
|
||||
counts.put(cn, oldValue + increment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by
|
||||
* {@link android.content.pm.ShortcutManager#setDynamicShortcuts},
|
||||
* {@link android.content.pm.ShortcutManager#addDynamicShortcuts}, and
|
||||
* {@link android.content.pm.ShortcutManager#updateShortcuts} before actually performing
|
||||
* the operation to make sure the operation wouldn't result in the target activities having
|
||||
* more than the allowed number of dynamic/manifest shortcuts.
|
||||
*
|
||||
* @param newList shortcut list passed to set, add or updateShortcuts().
|
||||
* @param operation add, set or update.
|
||||
* @throws IllegalArgumentException if the operation would result in going over the max
|
||||
* shortcut count for any activity.
|
||||
*/
|
||||
public void enforceShortcutCountsBeforeOperation(List<ShortcutInfo> newList,
|
||||
@ShortcutOperation int operation) {
|
||||
final ShortcutService service = mShortcutUser.mService;
|
||||
|
||||
// Current # of dynamic / manifest shortcuts for each activity.
|
||||
// (If it's for update, then don't count dynamic shortcuts, since they'll be replaced
|
||||
// anyway.)
|
||||
final ArrayMap<ComponentName, Integer> counts = new ArrayMap<>(4);
|
||||
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
|
||||
final ShortcutInfo shortcut = mShortcuts.valueAt(i);
|
||||
|
||||
if (shortcut.isManifestShortcut()) {
|
||||
incrementCountForActivity(counts, shortcut.getActivity(), 1);
|
||||
} else if (shortcut.isDynamic() && (operation != ShortcutService.OPERATION_SET)) {
|
||||
incrementCountForActivity(counts, shortcut.getActivity(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = newList.size() - 1; i >= 0; i--) {
|
||||
final ShortcutInfo newShortcut = newList.get(i);
|
||||
final ComponentName newActivity = newShortcut.getActivity();
|
||||
if (newActivity == null) {
|
||||
if (operation != ShortcutService.OPERATION_UPDATE) {
|
||||
service.wtf("null Activity found for non-update");
|
||||
}
|
||||
continue; // Activity can be null for update.
|
||||
}
|
||||
|
||||
final ShortcutInfo original = mShortcuts.get(newShortcut.getId());
|
||||
if (original == null) {
|
||||
if (operation == ShortcutService.OPERATION_UPDATE) {
|
||||
continue; // When updating, ignore if there's no target.
|
||||
}
|
||||
// Add() or set(), and there's no existing shortcut with the same ID. We're
|
||||
// simply publishing (as opposed to updating) this shortcut, so just +1.
|
||||
incrementCountForActivity(counts, newActivity, 1);
|
||||
continue;
|
||||
}
|
||||
if (original.isFloating() && (operation == ShortcutService.OPERATION_UPDATE)) {
|
||||
// Updating floating shortcuts doesn't affect the count, so ignore.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's add() or update(), then need to decrement for the previous activity.
|
||||
// Skip it for set() since it's already been taken care of by not counting the original
|
||||
// dynamic shortcuts in the first loop.
|
||||
if (operation != ShortcutService.OPERATION_SET) {
|
||||
final ComponentName oldActivity = original.getActivity();
|
||||
if (!original.isFloating()) {
|
||||
incrementCountForActivity(counts, oldActivity, -1);
|
||||
}
|
||||
}
|
||||
incrementCountForActivity(counts, newActivity, 1);
|
||||
}
|
||||
|
||||
// Then make sure none of the activities have more than the max number of shortcuts.
|
||||
for (int i = counts.size() - 1; i >= 0; i--) {
|
||||
service.enforceMaxActivityShortcuts(counts.valueAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
|
||||
pw.println();
|
||||
|
||||
@@ -995,4 +1177,46 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
List<ShortcutInfo> getAllShortcutsForTest() {
|
||||
return new ArrayList<>(mShortcuts.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verifyStates() {
|
||||
super.verifyStates();
|
||||
|
||||
boolean failed = false;
|
||||
|
||||
final ArrayMap<ComponentName, ArrayList<ShortcutInfo>> all =
|
||||
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()) {
|
||||
failed = true;
|
||||
Log.e(TAG, "Package " + getPackageName() + ": activity " + all.keyAt(i)
|
||||
+ " has " + all.valueAt(i).size() + " shortcuts.");
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
+ " is not manifest, dynamic or pinned.");
|
||||
}
|
||||
if (si.getActivity() == null) {
|
||||
failed = true;
|
||||
Log.e(TAG, "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()
|
||||
+ " is not floating, but is disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
throw new IllegalStateException("See logcat for errors");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,4 +132,10 @@ abstract class ShortcutPackageItem {
|
||||
|
||||
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
|
||||
throws IOException, XmlPullParserException;
|
||||
|
||||
/**
|
||||
* Verify various internal states.
|
||||
*/
|
||||
public void verifyStates() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ public class ShortcutParser {
|
||||
int type;
|
||||
|
||||
int rank = 0;
|
||||
final int maxShortcuts = service.getMaxActivityShortcuts();
|
||||
int numShortcuts = 0;
|
||||
|
||||
outer:
|
||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||
@@ -115,10 +117,17 @@ public class ShortcutParser {
|
||||
}
|
||||
|
||||
if (si != null) {
|
||||
if (numShortcuts >= maxShortcuts) {
|
||||
Slog.w(TAG, "More than " + maxShortcuts + " shortcuts found for "
|
||||
+ activityInfo.getComponentName() + ", ignoring the rest.");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
result.add(si);
|
||||
numShortcuts++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.server.pm;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
@@ -102,6 +103,8 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
@@ -114,8 +117,6 @@ import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Implement # of dynamic shortcuts cap.
|
||||
*
|
||||
* - HandleUnlockUser needs to be async. Wait on it in onCleanupUser.
|
||||
*
|
||||
* - Implement reportShortcutUsed().
|
||||
@@ -137,12 +138,12 @@ import java.util.function.Predicate;
|
||||
*
|
||||
* - Add more call stats.
|
||||
*
|
||||
* - Rename getMaxDynamicShortcutCount and mMaxDynamicShortcuts
|
||||
* - Rename mMaxDynamicShortcuts, because it includes manifest shortcuts too.
|
||||
*/
|
||||
public class ShortcutService extends IShortcutService.Stub {
|
||||
static final String TAG = "ShortcutService";
|
||||
|
||||
static final boolean DEBUG = true; // STOPSHIP if true
|
||||
static final boolean DEBUG = false; // STOPSHIP if true
|
||||
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
|
||||
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
|
||||
|
||||
@@ -329,6 +330,19 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
private static final int PROCESS_STATE_FOREGROUND_THRESHOLD =
|
||||
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
|
||||
|
||||
static final int OPERATION_SET = 0;
|
||||
static final int OPERATION_ADD = 1;
|
||||
static final int OPERATION_UPDATE = 2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(value = {
|
||||
OPERATION_SET,
|
||||
OPERATION_ADD,
|
||||
OPERATION_UPDATE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ShortcutOperation {}
|
||||
|
||||
public ShortcutService(Context context) {
|
||||
this(context, BackgroundThread.get().getLooper());
|
||||
}
|
||||
@@ -1312,14 +1326,22 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw if {@code numShortcuts} is bigger than {@link #mMaxDynamicShortcuts}.
|
||||
* @throws IllegalArgumentException if {@code numShortcuts} is bigger than
|
||||
* {@link #getMaxActivityShortcuts()}.
|
||||
*/
|
||||
void enforceMaxDynamicShortcuts(int numShortcuts) {
|
||||
void enforceMaxActivityShortcuts(int numShortcuts) {
|
||||
if (numShortcuts > mMaxDynamicShortcuts) {
|
||||
throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the max number of dynamic + manifest shortcuts for each launcher icon.
|
||||
*/
|
||||
int getMaxActivityShortcuts() {
|
||||
return mMaxDynamicShortcuts;
|
||||
}
|
||||
|
||||
/**
|
||||
* - Sends a notification to LauncherApps
|
||||
* - Write to file
|
||||
@@ -1440,11 +1462,12 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
|
||||
|
||||
// Throttling.
|
||||
if (!ps.tryApiCall()) {
|
||||
return false;
|
||||
}
|
||||
enforceMaxDynamicShortcuts(size);
|
||||
|
||||
// Validate the shortcuts.
|
||||
for (int i = 0; i < size; i++) {
|
||||
@@ -1461,6 +1484,9 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
|
||||
verifyStates();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1477,6 +1503,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
|
||||
|
||||
// Throttling.
|
||||
if (!ps.tryApiCall()) {
|
||||
return false;
|
||||
@@ -1514,6 +1542,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
|
||||
verifyStates();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1530,6 +1560,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
|
||||
|
||||
// Throttling.
|
||||
if (!ps.tryApiCall()) {
|
||||
return false;
|
||||
@@ -1546,6 +1578,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
|
||||
verifyStates();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1567,6 +1601,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
|
||||
verifyStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1584,6 +1620,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
|
||||
verifyStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1603,6 +1641,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
|
||||
verifyStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1613,6 +1653,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
|
||||
verifyStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2023,6 +2065,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
launcher.pinShortcuts(userId, packageName, shortcutIds);
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
|
||||
verifyStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2940,4 +2984,29 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
return pkg.findShortcutById(shortcutId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Control whether {@link #verifyStates} should be performed. We always perform it during unit
|
||||
* tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean injectShouldPerformVerification() {
|
||||
return DEBUG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check various internal states and throws if there's any inconsistency.
|
||||
* This is normally only enabled during unit tests.
|
||||
*/
|
||||
final void verifyStates() {
|
||||
if (injectShouldPerformVerification()) {
|
||||
verifyStatesInner();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyStatesInner() {
|
||||
synchronized (this) {
|
||||
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
services/tests/servicestests/res/xml/shortcut_1_alt.xml
Normal file
28
services/tests/servicestests/res/xml/shortcut_1_alt.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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="ms1-alt"
|
||||
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="data1"
|
||||
/>
|
||||
</shortcuts>
|
||||
@@ -1,3 +1,18 @@
|
||||
<?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="ms1"
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
<?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="ms1"
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
<?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="ms1"
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
<?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="ms1"
|
||||
|
||||
53
services/tests/servicestests/res/xml/shortcut_5_alt.xml
Normal file
53
services/tests/servicestests/res/xml/shortcut_5_alt.xml
Normal 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="ms1_alt"
|
||||
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="ms2_alt"
|
||||
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_alt"
|
||||
android:shortcutShortLabel="@string/shortcut_title1"
|
||||
android:shortcutIntentAction="android.intent.action.VIEW"
|
||||
/>
|
||||
<shortcut
|
||||
android:shortcutId="ms4_alt"
|
||||
android:shortcutShortLabel="@string/shortcut_title1"
|
||||
android:shortcutIntentAction="android.intent.action.VIEW"
|
||||
/>
|
||||
<shortcut
|
||||
android:shortcutId="ms5_alt"
|
||||
android:shortcutShortLabel="@string/shortcut_title1"
|
||||
android:shortcutIntentAction="android.intent.action.VIEW"
|
||||
/>
|
||||
</shortcuts>
|
||||
@@ -1,3 +1,18 @@
|
||||
<?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:shortcutShortLabel="@string/shortcut_title1"
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
<?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="manifest-shortcut-3"
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
<?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="manifest-shortcut-3"
|
||||
|
||||
@@ -17,37 +17,57 @@ package com.android.server.pm;
|
||||
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDisabled;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDynamic;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDynamicOrPinned;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllDynamicOrPinned;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllEnabled;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIcon;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIconFile;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIconResId;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveIntents;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllHaveIconFile;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllHaveIconResId;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllHaveIntents;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllHaveTitle;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllImmutable;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllKeyFieldsOnly;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllKeyFieldsOnly;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllManifest;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotHaveIntents;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotHaveTitle;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotKeyFieldsOnly;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllNotManifest;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllNotHaveIntents;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllNotHaveTitle;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllNotKeyFieldsOnly;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllNotManifest;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllPinned;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllStringsResolved;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertAllStringsResolved;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllUnique;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBitmapSize;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertBundleEmpty;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackNotReceived;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCallbackReceived;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertCannotUpdateImmutable;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicAndPinned;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertCallbackNotReceived;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertCallbackReceived;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertCannotUpdateImmutable;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertDynamicAndPinned;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicOnly;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
.assertDynamicShortcutCountExceeded;
|
||||
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
|
||||
.assertExpectException;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.cloneShortcutList;
|
||||
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;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makeBundle;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.parceled;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.pfdToBitmap;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAll;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
|
||||
@@ -155,8 +175,8 @@ import java.util.function.Consumer;
|
||||
|
||||
* TODO More tests for pinning + manifest shortcuts
|
||||
* TODO Manifest shortcuts + app upgrade -> launcher callback.
|
||||
* Also locale change should trigger launcehr callbacks too, when they use strign resoucres.
|
||||
* (not implemented yet.)
|
||||
* Also locale change should trigger launcher callbacks too, when they use strign resoucres.
|
||||
* (not implemented yet.)
|
||||
* TODO: Add checks with assertAllNotHaveIcon()
|
||||
* TODO: Detailed test for hasShortcutPermissionInner().
|
||||
* TODO: Add tests for the command line functions too.
|
||||
@@ -169,9 +189,9 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
* Whether to enable dump or not. Should be only true when debugging to avoid bugs where
|
||||
* dump affecting the behavior.
|
||||
*/
|
||||
private static final boolean ENABLE_DUMP = true; // DO NOT SUBMIT WITH true
|
||||
private static final boolean ENABLE_DUMP = false; // DO NOT SUBMIT WITH true
|
||||
|
||||
private static final boolean DUMP_IN_TEARDOWN = true; // DO NOT SUBMIT WITH true
|
||||
private static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
|
||||
|
||||
private static final String[] EMPTY_STRINGS = new String[0]; // Just for readability.
|
||||
|
||||
@@ -264,6 +284,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean injectShouldPerformVerification() {
|
||||
return true; // Always verify during unit tests.
|
||||
}
|
||||
|
||||
@Override
|
||||
String injectShortcutManagerConstants() {
|
||||
return ConfigConstants.KEY_RESET_INTERVAL_SEC + "=" + (INTERVAL / 1000) + ","
|
||||
@@ -407,6 +432,25 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
protected int injectMyUserId() {
|
||||
return UserHandle.getUserId(mInjectedCallingUid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
|
||||
// Note to simulate the binder RPC, we need to clone the incoming arguments.
|
||||
// Otherwise bad things will happen because they're mutable.
|
||||
return super.setDynamicShortcuts(cloneShortcutList(shortcutInfoList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
|
||||
// Note to simulate the binder RPC, we need to clone the incoming arguments.
|
||||
return super.addDynamicShortcuts(cloneShortcutList(shortcutInfoList));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) {
|
||||
// Note to simulate the binder RPC, we need to clone the incoming arguments.
|
||||
return super.updateShortcuts(cloneShortcutList(shortcutInfoList));
|
||||
}
|
||||
}
|
||||
|
||||
private class LauncherAppImplTestable extends LauncherAppsImpl {
|
||||
@@ -585,7 +629,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
|
||||
mInjectedCurrentTimeLillis = START_TIME;
|
||||
|
||||
mInjectedPackages = new HashMap<>();;
|
||||
mInjectedPackages = new HashMap<>();
|
||||
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1);
|
||||
addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 2);
|
||||
addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 3);
|
||||
@@ -929,9 +973,9 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
mService.dumpInner(pw, null);
|
||||
pw.close();
|
||||
|
||||
Log.e(TAG, "Dumping ShortcutService: " + message);
|
||||
Log.v(TAG, "Dumping ShortcutService: " + message);
|
||||
for (String line : out.toString().split("\n")) {
|
||||
Log.e(TAG, line);
|
||||
Log.v(TAG, line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,7 +989,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
private void dumpFileOnLogcat(String path, String message) {
|
||||
if (!ENABLE_DUMP) return;
|
||||
|
||||
Log.i(TAG, "Dumping file: " + path + " " + message);
|
||||
Log.v(TAG, "Dumping file: " + path + " " + message);
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
|
||||
String line;
|
||||
@@ -953,7 +997,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
Log.i(TAG, line);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Couldn't read file", e);
|
||||
Log.v(TAG, "Couldn't read file", e);
|
||||
fail("Exception " + e);
|
||||
}
|
||||
}
|
||||
@@ -982,7 +1026,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
private void waitOnMainThread() throws Throwable {
|
||||
runTestOnUiThread(() -> {});
|
||||
runTestOnUiThread(() -> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -991,13 +1036,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
private ShortcutInfo makeShortcut(String id) {
|
||||
return makeShortcut(
|
||||
id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
|
||||
}
|
||||
|
||||
private ShortcutInfo makeShortcutWithTitle(String id, String title) {
|
||||
return makeShortcut(
|
||||
id, title, /* activity =*/ null, /* icon =*/ null,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1006,7 +1051,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
private ShortcutInfo makeShortcutWithTimestamp(String id, long timestamp) {
|
||||
final ShortcutInfo s = makeShortcut(
|
||||
id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
|
||||
s.setTimestamp(timestamp);
|
||||
return s;
|
||||
}
|
||||
@@ -1018,7 +1063,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
ComponentName activity) {
|
||||
final ShortcutInfo s = makeShortcut(
|
||||
id, "Title-" + id, activity, /* icon =*/ null,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
|
||||
s.setTimestamp(timestamp);
|
||||
return s;
|
||||
}
|
||||
@@ -1029,7 +1074,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
private ShortcutInfo makeShortcutWithIcon(String id, Icon icon) {
|
||||
return makeShortcut(
|
||||
id, "Title-" + id, /* activity =*/ null, icon,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
|
||||
}
|
||||
|
||||
private ShortcutInfo makePackageShortcut(String packageName, String id) {
|
||||
@@ -1038,7 +1083,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
setCaller(packageName);
|
||||
ShortcutInfo s = makeShortcut(
|
||||
id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* weight =*/ 0);
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
|
||||
setCaller(origCaller); // restore the caller
|
||||
|
||||
return s;
|
||||
@@ -1059,6 +1104,26 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
return new ShortcutInfo.Builder(mClientContext);
|
||||
}
|
||||
|
||||
private ShortcutInfo makeShortcutWithActivity(String id, ComponentName activity) {
|
||||
return makeShortcut(
|
||||
id, "Title-" + id, activity, /* icon =*/ null,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
|
||||
}
|
||||
|
||||
private ShortcutInfo makeShortcutWithActivityAndTitle(String id, ComponentName activity,
|
||||
String title) {
|
||||
return makeShortcut(
|
||||
id, title, activity, /* icon =*/ null,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
|
||||
}
|
||||
|
||||
private ShortcutInfo makeShortcutWithActivityAndRank(String id, ComponentName activity,
|
||||
int rank) {
|
||||
return makeShortcut(
|
||||
id, "Title-" + id, activity, /* icon =*/ null,
|
||||
makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), rank);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a shortcut with details.
|
||||
*/
|
||||
@@ -1297,6 +1362,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
i.putExtra(Intent.EXTRA_REPLACING, true);
|
||||
return i;
|
||||
}
|
||||
|
||||
private Intent genPackageDataClear(String packageName, int userId) {
|
||||
Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
|
||||
i.setData(Uri.parse("package:" + packageName));
|
||||
@@ -1304,15 +1370,6 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
return i;
|
||||
}
|
||||
|
||||
private ShortcutInfo parceled(ShortcutInfo si) {
|
||||
Parcel p = Parcel.obtain();
|
||||
p.writeParcelable(si, 0);
|
||||
p.setDataPosition(0);
|
||||
ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
|
||||
p.recycle();
|
||||
return si2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for the first launch path, no settings file available.
|
||||
*/
|
||||
@@ -4509,7 +4566,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
|
||||
|
||||
mService.checkPackageChanges(USER_P0);
|
||||
|
||||
|
||||
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
|
||||
assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
|
||||
assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_0));
|
||||
@@ -7905,4 +7962,434 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
|
||||
});
|
||||
checkManifestShortcuts_immutable_verify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the APIs won't work on manifest shortcuts.
|
||||
*/
|
||||
public void testManifestShortcuts_tooMany() {
|
||||
// Change the max number of shortcuts.
|
||||
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
|
||||
|
||||
mService.handleUnlockUser(USER_0);
|
||||
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
||||
R.xml.shortcut_5);
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
// Only the first 3 should be published.
|
||||
assertShortcutIds(mManager.getManifestShortcuts(), "ms1", "ms2", "ms3");
|
||||
});
|
||||
}
|
||||
|
||||
public void testMaxShortcutCount_set() {
|
||||
// Change the max number of shortcuts.
|
||||
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
final ComponentName a1 = new ComponentName(mClientContext, ShortcutActivity.class);
|
||||
final ComponentName a2 = new ComponentName(mClientContext, ShortcutActivity2.class);
|
||||
final ShortcutInfo s1_1 = makeShortcutWithActivity("s11", a1);
|
||||
final ShortcutInfo s1_2 = makeShortcutWithActivity("s12", a1);
|
||||
final ShortcutInfo s1_3 = makeShortcutWithActivity("s13", a1);
|
||||
final ShortcutInfo s1_4 = makeShortcutWithActivity("s14", a1);
|
||||
final ShortcutInfo s1_5 = makeShortcutWithActivity("s15", a1);
|
||||
final ShortcutInfo s1_6 = makeShortcutWithActivity("s16", a1);
|
||||
final ShortcutInfo s2_1 = makeShortcutWithActivity("s21", a2);
|
||||
final ShortcutInfo s2_2 = makeShortcutWithActivity("s22", a2);
|
||||
final ShortcutInfo s2_3 = makeShortcutWithActivity("s23", a2);
|
||||
final ShortcutInfo s2_4 = makeShortcutWithActivity("s24", a2);
|
||||
|
||||
// 3 shortcuts for 2 activities -> okay
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13", "s21", "s22", "s23");
|
||||
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
|
||||
// 4 shortcut for activity 1 -> too many.
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s1_4, s2_1, s2_2, s2_3));
|
||||
});
|
||||
assertEmpty(mManager.getDynamicShortcuts());
|
||||
|
||||
// 4 shortcut for activity 2 -> too many.
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3, s2_4));
|
||||
});
|
||||
assertEmpty(mManager.getDynamicShortcuts());
|
||||
|
||||
// First, set 3. Then set 4, which should be ignored.
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.setDynamicShortcuts(list(s2_1, s2_2, s2_3, s2_4));
|
||||
});
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
|
||||
// Set will remove the old dynamic set, unlike add, so the following should pass.
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
mManager.setDynamicShortcuts(list(s1_4, s1_5, s1_6));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s14", "s15", "s16");
|
||||
|
||||
// Now, test with 2 manifest shortcuts.
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
||||
R.xml.shortcut_2);
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||
assertEquals(2, mManager.getManifestShortcuts().size());
|
||||
|
||||
// Setting 1 to activity 1 will work.
|
||||
mManager.setDynamicShortcuts(list(s1_1, s2_1, s2_2, s2_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s21", "s22", "s23");
|
||||
assertEquals(2, mManager.getManifestShortcuts().size());
|
||||
|
||||
// But setting 2 will not.
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s2_1, s2_2, s2_3));
|
||||
});
|
||||
assertEmpty(mManager.getDynamicShortcuts());
|
||||
assertEquals(2, mManager.getManifestShortcuts().size());
|
||||
});
|
||||
}
|
||||
|
||||
public void testMaxShortcutCount_add() {
|
||||
// Change the max number of shortcuts.
|
||||
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
final ComponentName a1 = new ComponentName(mClientContext, ShortcutActivity.class);
|
||||
final ComponentName a2 = new ComponentName(mClientContext, ShortcutActivity2.class);
|
||||
final ShortcutInfo s1_1 = makeShortcutWithActivity("s11", a1);
|
||||
final ShortcutInfo s1_2 = makeShortcutWithActivity("s12", a1);
|
||||
final ShortcutInfo s1_3 = makeShortcutWithActivity("s13", a1);
|
||||
final ShortcutInfo s1_4 = makeShortcutWithActivity("s14", a1);
|
||||
final ShortcutInfo s2_1 = makeShortcutWithActivity("s21", a2);
|
||||
final ShortcutInfo s2_2 = makeShortcutWithActivity("s22", a2);
|
||||
final ShortcutInfo s2_3 = makeShortcutWithActivity("s23", a2);
|
||||
final ShortcutInfo s2_4 = makeShortcutWithActivity("s24", a2);
|
||||
|
||||
// 3 shortcuts for 2 activities -> okay
|
||||
mManager.addDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13", "s21", "s22", "s23");
|
||||
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
;
|
||||
|
||||
// 4 shortcut for activity 1 -> too many.
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.addDynamicShortcuts(list(s1_1, s1_2, s1_3, s1_4, s2_1, s2_2, s2_3));
|
||||
});
|
||||
assertEmpty(mManager.getDynamicShortcuts());
|
||||
|
||||
// 4 shortcut for activity 2 -> too many.
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.addDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3, s2_4));
|
||||
});
|
||||
assertEmpty(mManager.getDynamicShortcuts());
|
||||
|
||||
// First, set 3. Then add 1 more, which should be ignored.
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.addDynamicShortcuts(list(s1_4, s2_1));
|
||||
});
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
|
||||
// Update existing one, which should work.
|
||||
mManager.addDynamicShortcuts(list(makeShortcutWithActivityAndTitle(
|
||||
"s11", a1, "xxx"), s2_1));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13", "s21");
|
||||
assertEquals("xxx", getCallerShortcut("s11").getTitle());
|
||||
|
||||
// Make sure pinned shortcuts won't affect.
|
||||
// - Pin s11 - s13, and remove all dynamic.
|
||||
runWithCaller(LAUNCHER_1, USER_0, () -> {
|
||||
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s11", "s12", "s13"),
|
||||
HANDLE_USER_0);
|
||||
});
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
|
||||
assertEmpty(mManager.getDynamicShortcuts());
|
||||
assertShortcutIds(mManager.getPinnedShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
|
||||
// Then add dynamic.
|
||||
mManager.addDynamicShortcuts(list(s1_4, s2_1, s2_2, s2_3));
|
||||
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s14", "s21", "s22", "s23");
|
||||
assertShortcutIds(mManager.getPinnedShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
|
||||
// Adding "s11" and "s12" back, should work
|
||||
mManager.addDynamicShortcuts(list(s1_1, s1_2));
|
||||
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s14", "s11", "s12", "s21", "s22", "s23");
|
||||
assertShortcutIds(mManager.getPinnedShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
|
||||
// Adding back s13 doesn't work.
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.addDynamicShortcuts(list(s1_3));
|
||||
});
|
||||
|
||||
assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a1),
|
||||
"s11", "s12", "s14");
|
||||
assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a2),
|
||||
"s21", "s22", "s23");
|
||||
|
||||
// Now swap the activities.
|
||||
mManager.updateShortcuts(list(
|
||||
makeShortcutWithActivity("s11", a2),
|
||||
makeShortcutWithActivity("s21", a1)));
|
||||
|
||||
assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a1),
|
||||
"s21", "s12", "s14");
|
||||
assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a2),
|
||||
"s11", "s22", "s23");
|
||||
|
||||
// Now, test with 2 manifest shortcuts.
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
||||
R.xml.shortcut_2);
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||
|
||||
assertEquals(2, mManager.getManifestShortcuts().size());
|
||||
|
||||
// Adding one shortcut to activity 1 works fine.
|
||||
mManager.addDynamicShortcuts(list(s1_1, s2_1, s2_2, s2_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s21", "s22", "s23");
|
||||
assertEquals(2, mManager.getManifestShortcuts().size());
|
||||
|
||||
// But adding one more doesn't.
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.addDynamicShortcuts(list(s1_4, s2_1));
|
||||
});
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s21", "s22", "s23");
|
||||
assertEquals(2, mManager.getManifestShortcuts().size());
|
||||
});
|
||||
}
|
||||
|
||||
public void testMaxShortcutCount_update() {
|
||||
// Change the max number of shortcuts.
|
||||
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
final ComponentName a1 = new ComponentName(mClientContext, ShortcutActivity.class);
|
||||
final ComponentName a2 = new ComponentName(mClientContext, ShortcutActivity2.class);
|
||||
final ShortcutInfo s1_1 = makeShortcutWithActivity("s11", a1);
|
||||
final ShortcutInfo s1_2 = makeShortcutWithActivity("s12", a1);
|
||||
final ShortcutInfo s1_3 = makeShortcutWithActivity("s13", a1);
|
||||
final ShortcutInfo s1_4 = makeShortcutWithActivity("s14", a1);
|
||||
final ShortcutInfo s1_5 = makeShortcutWithActivity("s15", a1);
|
||||
final ShortcutInfo s2_1 = makeShortcutWithActivity("s21", a2);
|
||||
final ShortcutInfo s2_2 = makeShortcutWithActivity("s22", a2);
|
||||
final ShortcutInfo s2_3 = makeShortcutWithActivity("s23", a2);
|
||||
final ShortcutInfo s2_4 = makeShortcutWithActivity("s24", a2);
|
||||
|
||||
// 3 shortcuts for 2 activities -> okay
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13", "s21", "s22", "s23");
|
||||
|
||||
// Trying to move s11 from a1 to a2 should fail.
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.updateShortcuts(list(makeShortcutWithActivity("s11", a2)));
|
||||
});
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13", "s21", "s22", "s23");
|
||||
|
||||
// Trying to move s21 from a2 to a1 should also fail.
|
||||
assertDynamicShortcutCountExceeded(() -> {
|
||||
mManager.updateShortcuts(list(makeShortcutWithActivity("s21", a1)));
|
||||
});
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13", "s21", "s22", "s23");
|
||||
|
||||
// But, if we do these two at the same time, it should work.
|
||||
mManager.updateShortcuts(list(
|
||||
makeShortcutWithActivity("s11", a2),
|
||||
makeShortcutWithActivity("s21", a1)));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13", "s21", "s22", "s23");
|
||||
assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a1),
|
||||
"s21", "s12", "s13");
|
||||
assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a2),
|
||||
"s11", "s22", "s23");
|
||||
|
||||
// Then reset.
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s11", "s12", "s13", "s21", "s22", "s23");
|
||||
|
||||
// Pin some to have more shortcuts for a1.
|
||||
runWithCaller(LAUNCHER_1, USER_0, () -> {
|
||||
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s11", "s12", "s13"),
|
||||
HANDLE_USER_0);
|
||||
});
|
||||
mManager.setDynamicShortcuts(list(s1_4, s1_5, s2_1, s2_2, s2_3));
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s14", "s15", "s21", "s22", "s23");
|
||||
assertShortcutIds(mManager.getPinnedShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
|
||||
// a1 already has 2 dynamic shortcuts (and 3 pinned shortcuts that used to belong on it)
|
||||
// But that doesn't matter for update -- the following should still work.
|
||||
mManager.updateShortcuts(list(
|
||||
makeShortcutWithActivityAndTitle("s11", a1, "xxx1"),
|
||||
makeShortcutWithActivityAndTitle("s12", a1, "xxx2"),
|
||||
makeShortcutWithActivityAndTitle("s13", a1, "xxx3"),
|
||||
makeShortcutWithActivityAndTitle("s14", a1, "xxx4"),
|
||||
makeShortcutWithActivityAndTitle("s15", a1, "xxx5")));
|
||||
// All the shortcuts should still exist they all belong on same activities,
|
||||
// with the updated titles.
|
||||
assertShortcutIds(mManager.getDynamicShortcuts(),
|
||||
"s14", "s15", "s21", "s22", "s23");
|
||||
assertShortcutIds(mManager.getPinnedShortcuts(),
|
||||
"s11", "s12", "s13");
|
||||
|
||||
assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a1),
|
||||
"s14", "s15");
|
||||
assertShortcutIds(filterByActivity(mManager.getDynamicShortcuts(), a2),
|
||||
"s21", "s22", "s23");
|
||||
|
||||
assertEquals("xxx1", getCallerShortcut("s11").getTitle());
|
||||
assertEquals("xxx2", getCallerShortcut("s12").getTitle());
|
||||
assertEquals("xxx3", getCallerShortcut("s13").getTitle());
|
||||
assertEquals("xxx4", getCallerShortcut("s14").getTitle());
|
||||
assertEquals("xxx5", getCallerShortcut("s15").getTitle());
|
||||
});
|
||||
}
|
||||
|
||||
public void testShortcutsPushedOutByManifest() {
|
||||
// Change the max number of shortcuts.
|
||||
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
final ComponentName a1 = new ComponentName(mClientContext, ShortcutActivity.class);
|
||||
final ComponentName a2 = new ComponentName(mClientContext, ShortcutActivity2.class);
|
||||
final ShortcutInfo s1_1 = makeShortcutWithActivityAndRank("s11", a1, 4);
|
||||
final ShortcutInfo s1_2 = makeShortcutWithActivityAndRank("s12", a1, 3);
|
||||
final ShortcutInfo s1_3 = makeShortcutWithActivityAndRank("s13", a1, 2);
|
||||
final ShortcutInfo s1_4 = makeShortcutWithActivityAndRank("s14", a1, 1);
|
||||
final ShortcutInfo s1_5 = makeShortcutWithActivityAndRank("s15", a1, 0);
|
||||
final ShortcutInfo s2_1 = makeShortcutWithActivityAndRank("s21", a2, 0);
|
||||
final ShortcutInfo s2_2 = makeShortcutWithActivityAndRank("s22", a2, 1);
|
||||
final ShortcutInfo s2_3 = makeShortcutWithActivityAndRank("s23", a2, 2);
|
||||
final ShortcutInfo s2_4 = makeShortcutWithActivityAndRank("s24", a2, 3);
|
||||
final ShortcutInfo s2_5 = makeShortcutWithActivityAndRank("s25", a2, 4);
|
||||
|
||||
// Initial state.
|
||||
mManager.setDynamicShortcuts(list(s1_1, s1_2, s1_3, s2_1, s2_2, s2_3));
|
||||
runWithCaller(LAUNCHER_1, USER_0, () -> {
|
||||
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s11", "s12", "s21", "s22"),
|
||||
HANDLE_USER_0);
|
||||
});
|
||||
mManager.setDynamicShortcuts(list(s1_2, s1_3, s1_4, s2_2, s2_3, s2_4));
|
||||
assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
|
||||
"s12", "s13", "s14",
|
||||
"s22", "s23", "s24");
|
||||
assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
|
||||
"s11", "s12",
|
||||
"s21", "s22");
|
||||
|
||||
// Add 1 manifest shortcut to a1.
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
||||
R.xml.shortcut_1);
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||
assertEquals(1, mManager.getManifestShortcuts().size());
|
||||
|
||||
// s12 removed.
|
||||
assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
|
||||
"s13", "s14",
|
||||
"s22", "s23", "s24");
|
||||
assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
|
||||
"s11", "s12",
|
||||
"s21", "s22");
|
||||
|
||||
// Add more manifest shortcuts.
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
||||
R.xml.shortcut_2);
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
|
||||
R.xml.shortcut_1_alt);
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||
assertEquals(3, mManager.getManifestShortcuts().size());
|
||||
|
||||
// Note the ones with the highest rank values (== least important) will be removed.
|
||||
assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
|
||||
"s14",
|
||||
"s22", "s23");
|
||||
assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
|
||||
"s11", "s12",
|
||||
"s21", "s22");
|
||||
|
||||
// Add more manifest shortcuts.
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
||||
R.xml.shortcut_2);
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
|
||||
R.xml.shortcut_5_alt); // manifest has 5, but max is 3, so a2 will have 3.
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||
assertEquals(5, mManager.getManifestShortcuts().size());
|
||||
|
||||
assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
|
||||
"s14" // a1 has 1 dynamic
|
||||
); // a2 has no dynamic
|
||||
assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
|
||||
"s11", "s12",
|
||||
"s21", "s22");
|
||||
|
||||
// Update, no manifest shortucts. This doesn't affect anything.
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
||||
R.xml.shortcut_0);
|
||||
addManifestShortcutResource(
|
||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity2.class.getName()),
|
||||
R.xml.shortcut_0);
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||
assertEquals(0, mManager.getManifestShortcuts().size());
|
||||
|
||||
assertShortcutIds(assertAllEnabled(mManager.getDynamicShortcuts()),
|
||||
"s14");
|
||||
assertShortcutIds(assertAllEnabled(mManager.getPinnedShortcuts()),
|
||||
"s11", "s12",
|
||||
"s21", "s22");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
@@ -38,6 +39,7 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.BaseBundle;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.UserHandle;
|
||||
import android.test.MoreAsserts;
|
||||
@@ -252,6 +254,17 @@ public class ShortcutManagerTestUtils {
|
||||
return list;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
|
||||
String expectedExceptionMessageRegex, Runnable r) {
|
||||
assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r);
|
||||
@@ -555,6 +568,27 @@ public class ShortcutManagerTestUtils {
|
||||
}, "Shortcut IDs=[" + Arrays.toString(ids) + "]", null);
|
||||
}
|
||||
|
||||
public static ShortcutInfo parceled(ShortcutInfo si) {
|
||||
Parcel p = Parcel.obtain();
|
||||
p.writeParcelable(si, 0);
|
||||
p.setDataPosition(0);
|
||||
ShortcutInfo si2 = p.readParcelable(ShortcutManagerTestUtils.class.getClassLoader());
|
||||
p.recycle();
|
||||
return si2;
|
||||
}
|
||||
|
||||
public static List<ShortcutInfo> cloneShortcutList(List<ShortcutInfo> list) {
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
final List<ShortcutInfo> ret = new ArrayList<>(list.size());
|
||||
for (ShortcutInfo si : list) {
|
||||
ret.add(parceled(si));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void waitUntil(String message, BooleanSupplier condition) {
|
||||
waitUntil(message, condition, STANDARD_TIMEOUT_SEC);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user