Merge \"Shortcut: Only \"main\" activities can have shortcuts.\" into nyc-mr1-dev
am: 1af9a58956
Change-Id: Id878608aec49985228e129de9736b751c9ae8567
This commit is contained in:
@@ -709,7 +709,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
@NonNull
|
||||
@Deprecated
|
||||
public Builder setId(@NonNull String id) {
|
||||
mId = Preconditions.checkStringNotEmpty(id, "id");
|
||||
mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -721,14 +721,17 @@ public final class ShortcutInfo implements Parcelable {
|
||||
*/
|
||||
public Builder(Context context, String id) {
|
||||
mContext = context;
|
||||
mId = Preconditions.checkStringNotEmpty(id, "id");
|
||||
mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the target activity. A shortcut will be shown with this activity on the launcher.
|
||||
*
|
||||
* <p>This is a mandatory field, unless it's passed to
|
||||
* {@link ShortcutManager#updateShortcuts(List)}.
|
||||
* <p>Only "main" activities -- i.e. ones with an intent filter for
|
||||
* {@link Intent#ACTION_MAIN} and {@link Intent#CATEGORY_LAUNCHER} can be target activities.
|
||||
*
|
||||
* <p>By default, the first main activity defined in the application manifest will be
|
||||
* the target.
|
||||
*
|
||||
* <p>The package name of the target activity must match the package name of the shortcut
|
||||
* publisher.
|
||||
@@ -738,7 +741,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setActivity(@NonNull ComponentName activity) {
|
||||
mActivity = Preconditions.checkNotNull(activity, "activity");
|
||||
mActivity = Preconditions.checkNotNull(activity, "activity cannot be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -785,7 +788,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
@NonNull
|
||||
public Builder setShortLabel(@NonNull CharSequence shortLabel) {
|
||||
Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set");
|
||||
mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel");
|
||||
mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -810,7 +813,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
@NonNull
|
||||
public Builder setLongLabel(@NonNull CharSequence longLabel) {
|
||||
Preconditions.checkState(mTextResId == 0, "longLabelResId already set");
|
||||
mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel");
|
||||
mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -854,7 +857,8 @@ public final class ShortcutInfo implements Parcelable {
|
||||
Preconditions.checkState(
|
||||
mDisabledMessageResId == 0, "disabledMessageResId already set");
|
||||
mDisabledMessage =
|
||||
Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage");
|
||||
Preconditions.checkStringNotEmpty(disabledMessage,
|
||||
"disabledMessage cannot be empty");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -876,8 +880,8 @@ 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");
|
||||
mIntent = Preconditions.checkNotNull(intent, "intent cannot be null");
|
||||
Preconditions.checkNotNull(mIntent.getAction(), "intent's action must be set");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -944,6 +948,11 @@ public final class ShortcutInfo implements Parcelable {
|
||||
return mActivity;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setActivity(ComponentName activity) {
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon.
|
||||
*
|
||||
|
||||
@@ -54,8 +54,6 @@ import java.util.function.Predicate;
|
||||
* User information used by {@link ShortcutService}.
|
||||
*
|
||||
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
|
||||
*
|
||||
* TODO Max dynamic shortcuts cap should be per activity.
|
||||
*/
|
||||
class ShortcutPackage extends ShortcutPackageItem {
|
||||
private static final String TAG = ShortcutService.TAG;
|
||||
@@ -321,9 +319,27 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return true if it's actually removed because it wasn't pinned, or false if it's still
|
||||
* pinned.
|
||||
*/
|
||||
public void deleteDynamicWithId(@NonNull String shortcutId) {
|
||||
deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
|
||||
public boolean deleteDynamicWithId(@NonNull String shortcutId) {
|
||||
final ShortcutInfo removed = deleteOrDisableWithId(
|
||||
shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
|
||||
return removed == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return true if it's actually removed because it wasn't pinned, or false if it's still
|
||||
* pinned.
|
||||
*/
|
||||
private boolean disableDynamicWithId(@NonNull String shortcutId) {
|
||||
final ShortcutInfo disabled = deleteOrDisableWithId(
|
||||
shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false);
|
||||
return disabled == null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -599,14 +615,14 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
*
|
||||
* @return TRUE if any shortcuts have been changed.
|
||||
*/
|
||||
public boolean handlePackageAddedOrUpdated(boolean isNewApp) {
|
||||
public boolean handlePackageAddedOrUpdated(boolean isNewApp, boolean forceRescan) {
|
||||
final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
|
||||
getPackageName(), getPackageUserId());
|
||||
if (pi == null) {
|
||||
return false; // Shouldn't happen.
|
||||
}
|
||||
|
||||
if (!isNewApp) {
|
||||
if (!isNewApp && !forceRescan) {
|
||||
// Make sure the version code or last update time has changed.
|
||||
// Otherwise, nothing to do.
|
||||
if (getPackageInfo().getVersionCode() >= pi.versionCode
|
||||
@@ -649,12 +665,26 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
boolean changed = false;
|
||||
|
||||
// For existing shortcuts, update timestamps if they have any resources.
|
||||
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
|
||||
if (!isNewApp) {
|
||||
Resources publisherRes = null;
|
||||
|
||||
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
|
||||
final ShortcutInfo si = mShortcuts.valueAt(i);
|
||||
|
||||
if (si.isDynamic()) {
|
||||
if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) {
|
||||
Slog.w(TAG, String.format(
|
||||
"%s is no longer main activity. Disabling shorcut %s.",
|
||||
getPackageName(), si.getId()));
|
||||
if (disableDynamicWithId(si.getId())) {
|
||||
continue; // Actually removed.
|
||||
}
|
||||
// Still pinned, so fall-through and possibly update the resources.
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (si.hasAnyResources()) {
|
||||
if (!si.isOriginallyFromManifest()) {
|
||||
if (publisherRes == null) {
|
||||
@@ -912,9 +942,8 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
final ComponentName newActivity = newShortcut.getActivity();
|
||||
if (newActivity == null) {
|
||||
if (operation != ShortcutService.OPERATION_UPDATE) {
|
||||
// This method may be called before validating shortcuts, so this may happen,
|
||||
// and is a caller side error.
|
||||
throw new NullPointerException("Activity must be provided");
|
||||
service.wtf("Activity must not be null at this point");
|
||||
continue; // Just ignore this invalid case.
|
||||
}
|
||||
continue; // Activity can be null for update.
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XmlResourceParser;
|
||||
@@ -58,18 +59,36 @@ public class ShortcutParser {
|
||||
@Nullable
|
||||
public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
|
||||
String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException {
|
||||
final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId);
|
||||
if (ShortcutService.DEBUG) {
|
||||
Slog.d(TAG, String.format("Scanning package %s for manifest shortcuts on user %d",
|
||||
packageName, userId));
|
||||
}
|
||||
final List<ResolveInfo> activities = service.injectGetMainActivities(packageName, userId);
|
||||
if (activities == null || activities.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ShortcutInfo> result = null;
|
||||
|
||||
try {
|
||||
if (pi != null && pi.activities != null) {
|
||||
for (ActivityInfo activityInfo : pi.activities) {
|
||||
result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
|
||||
final int size = activities.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
final ActivityInfo activityInfoNoMetadata = activities.get(i).activityInfo;
|
||||
if (activityInfoNoMetadata == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final ActivityInfo activityInfoWithMetadata =
|
||||
service.injectGetActivityInfoWithMetadata(
|
||||
activityInfoNoMetadata.getComponentName(), userId);
|
||||
if (activityInfoWithMetadata != null) {
|
||||
result = parseShortcutsOneFile(
|
||||
service, activityInfoWithMetadata, packageName, userId, result);
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// Resource ID mismatch may cause various runtime exceptions when parsing XMLs.
|
||||
// Resource ID mismatch may cause various runtime exceptions when parsing XMLs,
|
||||
// But we don't crash the device, so just swallow them.
|
||||
service.wtf(
|
||||
"Exception caught while parsing shortcut XML for package=" + packageName, e);
|
||||
return null;
|
||||
@@ -81,6 +100,11 @@ public class ShortcutParser {
|
||||
ShortcutService service,
|
||||
ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
|
||||
List<ShortcutInfo> result) throws IOException, XmlPullParserException {
|
||||
if (ShortcutService.DEBUG) {
|
||||
Slog.d(TAG, String.format(
|
||||
"Checking main activity %s", activityInfo.getComponentName()));
|
||||
}
|
||||
|
||||
XmlResourceParser parser = null;
|
||||
try {
|
||||
parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
|
||||
@@ -223,7 +247,7 @@ public class ShortcutParser {
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.w(TAG, "Unknown tag " + tag + " at depth " + depth);
|
||||
ShortcutService.warnForInvalidTag(depth, tag);
|
||||
}
|
||||
} finally {
|
||||
if (parser != null) {
|
||||
|
||||
@@ -60,6 +60,7 @@ import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.SELinux;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ShellCommand;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
@@ -76,6 +77,7 @@ import android.util.SparseIntArray;
|
||||
import android.util.SparseLongArray;
|
||||
import android.util.TypedValue;
|
||||
import android.util.Xml;
|
||||
import android.view.IWindowManager;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -129,7 +131,7 @@ import java.util.function.Predicate;
|
||||
* - Default launcher check does take a few ms. Worth caching.
|
||||
*
|
||||
* - Detect when already registered instances are passed to APIs again, which might break
|
||||
* internal bitmap handling.
|
||||
* internal bitmap handling.
|
||||
*
|
||||
* - Add more call stats.
|
||||
*/
|
||||
@@ -181,6 +183,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
private static final String ATTR_VALUE = "value";
|
||||
|
||||
private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER;
|
||||
|
||||
@VisibleForTesting
|
||||
interface ConfigConstants {
|
||||
/**
|
||||
@@ -282,7 +286,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
private List<Integer> mDirtyUserIds = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* A counter that increments every time the system locale changes. We keep track of it to reset
|
||||
* A counter that increments every time the system locale changes. We keep track of it to
|
||||
* reset
|
||||
* throttling counters on the first call from each package after the last locale change.
|
||||
*
|
||||
* We need this mechanism because we can't do much in the locale change callback, which is
|
||||
@@ -294,8 +299,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
private static final int PACKAGE_MATCH_FLAGS =
|
||||
PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
|
||||
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
|
||||
|
||||
// Stats
|
||||
@VisibleForTesting
|
||||
@@ -306,13 +311,15 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
int GET_APPLICATION_INFO = 3;
|
||||
int LAUNCHER_PERMISSION_CHECK = 4;
|
||||
int CLEANUP_DANGLING_BITMAPS = 5;
|
||||
int GET_ACTIVITIES_WITH_METADATA = 6;
|
||||
int GET_ACTIVITY_WITH_METADATA = 6;
|
||||
int GET_INSTALLED_PACKAGES = 7;
|
||||
int CHECK_PACKAGE_CHANGES = 8;
|
||||
int GET_APPLICATION_RESOURCES = 9;
|
||||
int RESOURCE_NAME_LOOKUP = 10;
|
||||
int GET_LAUNCHER_ACTIVITY = 11;
|
||||
int CHECK_LAUNCHER_ACTIVITY = 12;
|
||||
|
||||
int COUNT = RESOURCE_NAME_LOOKUP + 1;
|
||||
int COUNT = CHECK_LAUNCHER_ACTIVITY + 1;
|
||||
}
|
||||
|
||||
final Object mStatLock = new Object();
|
||||
@@ -335,9 +342,10 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
OPERATION_SET,
|
||||
OPERATION_ADD,
|
||||
OPERATION_UPDATE
|
||||
})
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ShortcutOperation {}
|
||||
@interface ShortcutOperation {
|
||||
}
|
||||
|
||||
public ShortcutService(Context context) {
|
||||
this(context, BackgroundThread.get().getLooper());
|
||||
@@ -373,18 +381,22 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
|
||||
final private IUidObserver mUidObserver = new IUidObserver.Stub() {
|
||||
@Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
|
||||
@Override
|
||||
public void onUidStateChanged(int uid, int procState) throws RemoteException {
|
||||
handleOnUidStateChanged(uid, procState);
|
||||
}
|
||||
|
||||
@Override public void onUidGone(int uid) throws RemoteException {
|
||||
@Override
|
||||
public void onUidGone(int uid) throws RemoteException {
|
||||
handleOnUidStateChanged(uid, ActivityManager.MAX_PROCESS_STATE);
|
||||
}
|
||||
|
||||
@Override public void onUidActive(int uid) throws RemoteException {
|
||||
@Override
|
||||
public void onUidActive(int uid) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override public void onUidIdle(int uid) throws RemoteException {
|
||||
@Override
|
||||
public void onUidIdle(int uid) throws RemoteException {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -555,11 +567,11 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
|
||||
? (int) parser.getLong(
|
||||
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
|
||||
DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
|
||||
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
|
||||
DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
|
||||
: (int) parser.getLong(
|
||||
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
|
||||
DEFAULT_MAX_ICON_DIMENSION_DP));
|
||||
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
|
||||
DEFAULT_MAX_ICON_DIMENSION_DP));
|
||||
|
||||
mMaxIconDimension = injectDipToPixel(iconDimensionDp);
|
||||
|
||||
@@ -777,7 +789,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// Use the default
|
||||
} catch (IOException|XmlPullParserException e) {
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
|
||||
|
||||
mRawLastResetTime = 0;
|
||||
@@ -800,7 +812,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
saveUserInternalLocked(userId, os, /* forBackup= */ false);
|
||||
|
||||
file.finishWrite(os);
|
||||
} catch (XmlPullParserException|IOException e) {
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
|
||||
file.failWrite(os);
|
||||
}
|
||||
@@ -850,10 +862,10 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
|
||||
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
|
||||
cleanupDanglingBitmapDirectoriesLocked(userId, ret);
|
||||
return ret;
|
||||
} catch (IOException|XmlPullParserException e) {
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
|
||||
return null;
|
||||
} finally {
|
||||
@@ -1133,7 +1145,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
|
||||
final String baseName = String.valueOf(injectCurrentTimeMillis());
|
||||
for (int suffix = 0;; suffix++) {
|
||||
for (int suffix = 0; ; suffix++) {
|
||||
final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
|
||||
final File file = new File(packagePath, filename);
|
||||
if (!file.exists()) {
|
||||
@@ -1205,7 +1217,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
} finally {
|
||||
IoUtils.closeQuietly(out);
|
||||
}
|
||||
} catch (IOException|RuntimeException e) {
|
||||
} catch (IOException | RuntimeException e) {
|
||||
// STOPSHIP Change wtf to e
|
||||
Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
|
||||
if (path != null && path.exists()) {
|
||||
@@ -1284,7 +1296,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
private boolean isCallerSystem() {
|
||||
final int callingUid = injectBinderCallingUid();
|
||||
return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
|
||||
return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
|
||||
}
|
||||
|
||||
private boolean isCallerShell() {
|
||||
@@ -1349,7 +1361,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if {@code numShortcuts} is bigger than
|
||||
* {@link #getMaxActivityShortcuts()}.
|
||||
* {@link #getMaxActivityShortcuts()}.
|
||||
*/
|
||||
void enforceMaxActivityShortcuts(int numShortcuts) {
|
||||
if (numShortcuts > mMaxShortcuts) {
|
||||
@@ -1402,7 +1414,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
* Clean up / validate an incoming shortcut.
|
||||
* - Make sure all mandatory fields are set.
|
||||
* - Make sure the intent's extras are persistable, and them to set
|
||||
* {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras.
|
||||
* {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras.
|
||||
* - Clear flags.
|
||||
*
|
||||
* TODO Detailed unit tests
|
||||
@@ -1412,11 +1424,15 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
if (shortcut.getActivity() != null) {
|
||||
Preconditions.checkState(
|
||||
shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
|
||||
"Activity package name mismatch");
|
||||
"Cannot publish shortcut: activity " + shortcut.getActivity() + " does not"
|
||||
+ " belong to package " + shortcut.getPackage());
|
||||
}
|
||||
|
||||
if (!forUpdate) {
|
||||
shortcut.enforceMandatoryFields();
|
||||
Preconditions.checkArgument(
|
||||
injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()),
|
||||
"Cannot publish shortcut: " + shortcut.getActivity() + " is not main activity");
|
||||
}
|
||||
if (shortcut.getIcon() != null) {
|
||||
ShortcutInfo.validateIcon(shortcut.getIcon());
|
||||
@@ -1425,6 +1441,26 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
shortcut.replaceFlags(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a shortcut has no target activity, set the default one from the package.
|
||||
*/
|
||||
private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) {
|
||||
|
||||
ComponentName defaultActivity = null;
|
||||
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
||||
final ShortcutInfo si = shortcuts.get(i);
|
||||
if (si.getActivity() == null) {
|
||||
if (defaultActivity == null) {
|
||||
defaultActivity = injectGetDefaultMainActivity(
|
||||
si.getPackage(), si.getUserId());
|
||||
Preconditions.checkState(defaultActivity != null,
|
||||
"Launcher activity not found for package " + si.getPackage());
|
||||
}
|
||||
si.setActivity(defaultActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assignImplicitRanks(List<ShortcutInfo> shortcuts) {
|
||||
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
||||
shortcuts.get(i).setImplicitRank(i);
|
||||
@@ -1446,6 +1482,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
fillInDefaultActivity(newShortcuts);
|
||||
|
||||
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
|
||||
|
||||
// Throttling.
|
||||
@@ -1493,6 +1531,9 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
// For update, don't fill in the default activity. Having null activity means
|
||||
// "don't update the activity" here.
|
||||
|
||||
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
|
||||
|
||||
// Throttling.
|
||||
@@ -1573,6 +1614,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
fillInDefaultActivity(newShortcuts);
|
||||
|
||||
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
|
||||
|
||||
// Initialize the implicit ranks for ShortcutPackage.adjustRanks().
|
||||
@@ -1795,7 +1838,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all throttling, for developer options and command line. Only system/shell can call it.
|
||||
* Reset all throttling, for developer options and command line. Only system/shell can call
|
||||
* it.
|
||||
*/
|
||||
@Override
|
||||
public void resetThrottling() {
|
||||
@@ -1917,10 +1961,12 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
// === House keeping ===
|
||||
|
||||
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId) {
|
||||
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
|
||||
boolean appStillExists) {
|
||||
synchronized (mLock) {
|
||||
forEachLoadedUserLocked(user ->
|
||||
cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
|
||||
cleanUpPackageLocked(packageName, user.getUserId(), packageUserId,
|
||||
appStillExists));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1932,7 +1978,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
* This is called when an app is uninstalled, or an app gets "clear data"ed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
|
||||
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
|
||||
boolean appStillExists) {
|
||||
final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
|
||||
|
||||
final ShortcutUser user = getUserShortcutsLocked(owningUserId);
|
||||
@@ -1961,6 +2008,13 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
notifyListeners(packageName, owningUserId);
|
||||
}
|
||||
|
||||
// If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts.
|
||||
if (appStillExists && (packageUserId == owningUserId)) {
|
||||
// This will do the notification and save when needed, so do it after the above
|
||||
// notifyListeners.
|
||||
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ true);
|
||||
}
|
||||
|
||||
if (!wasUserLoaded) {
|
||||
// Note this will execute the scheduled save.
|
||||
unloadUserLocked(owningUserId);
|
||||
@@ -2269,19 +2323,29 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
public void onPackageDataCleared(String packageName, int uid) {
|
||||
handlePackageDataCleared(packageName, getChangingUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPackageChanged(String packageName, int uid, String[] components) {
|
||||
handlePackageChanged(packageName, getChangingUserId());
|
||||
return false; // We don't need to receive onSomePackagesChanged(), so just false.
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when a user is unlocked.
|
||||
* - Check all known packages still exist, and otherwise perform cleanup.
|
||||
* - If a package still exists, check the version code. If it's been updated, may need to
|
||||
* update timestamps of its shortcuts.
|
||||
* update timestamps of its shortcuts.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void checkPackageChanges(@UserIdInt int ownerUserId) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
|
||||
}
|
||||
if (injectIsSafeModeEnabled()) {
|
||||
Slog.i(TAG, "Safe mode, skipping checkPackageChanges()");
|
||||
return;
|
||||
}
|
||||
|
||||
final long start = injectElapsedRealtime();
|
||||
try {
|
||||
@@ -2302,14 +2366,15 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
if (gonePackages.size() > 0) {
|
||||
for (int i = gonePackages.size() - 1; i >= 0; i--) {
|
||||
final PackageWithUser pu = gonePackages.get(i);
|
||||
cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
|
||||
cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId,
|
||||
/* appStillExists = */ false);
|
||||
}
|
||||
}
|
||||
final long now = injectCurrentTimeMillis();
|
||||
|
||||
// Then for each installed app, publish manifest shortcuts when needed.
|
||||
forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
|
||||
user.handlePackageAddedOrUpdated(ai.packageName);
|
||||
user.handlePackageAddedOrUpdated(ai.packageName, /* forceRescan=*/ false);
|
||||
});
|
||||
|
||||
// Write the time just before the scan, because there may be apps that have just
|
||||
@@ -2330,7 +2395,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
synchronized (mLock) {
|
||||
final ShortcutUser user = getUserShortcutsLocked(userId);
|
||||
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
|
||||
user.handlePackageAddedOrUpdated(packageName);
|
||||
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
|
||||
}
|
||||
verifyStates();
|
||||
}
|
||||
@@ -2345,7 +2410,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
|
||||
|
||||
if (isPackageInstalled(packageName, userId)) {
|
||||
user.handlePackageAddedOrUpdated(packageName);
|
||||
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
|
||||
}
|
||||
}
|
||||
verifyStates();
|
||||
@@ -2356,7 +2421,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
|
||||
packageUserId));
|
||||
}
|
||||
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
|
||||
cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false);
|
||||
|
||||
verifyStates();
|
||||
}
|
||||
@@ -2366,7 +2431,23 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
|
||||
packageUserId));
|
||||
}
|
||||
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
|
||||
cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true);
|
||||
|
||||
verifyStates();
|
||||
}
|
||||
|
||||
private void handlePackageChanged(String packageName, int packageUserId) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName,
|
||||
packageUserId));
|
||||
}
|
||||
|
||||
// Activities may be disabled or enabled. Just rescan the package.
|
||||
synchronized (mLock) {
|
||||
final ShortcutUser user = getUserShortcutsLocked(packageUserId);
|
||||
|
||||
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ true);
|
||||
}
|
||||
|
||||
verifyStates();
|
||||
}
|
||||
@@ -2405,7 +2486,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
final long token = injectClearCallingIdentity();
|
||||
try {
|
||||
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
|
||||
| (getSignatures ? PackageManager.GET_SIGNATURES : 0)
|
||||
| (getSignatures ? PackageManager.GET_SIGNATURES : 0)
|
||||
, userId);
|
||||
} catch (RemoteException e) {
|
||||
// Shouldn't happen.
|
||||
@@ -2439,14 +2520,12 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
|
||||
ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) {
|
||||
final long start = injectElapsedRealtime();
|
||||
final long token = injectClearCallingIdentity();
|
||||
try {
|
||||
return mIPackageManager.getPackageInfo(packageName,
|
||||
PACKAGE_MATCH_FLAGS | PackageManager.GET_ACTIVITIES
|
||||
| PackageManager.GET_META_DATA, userId);
|
||||
return mIPackageManager.getActivityInfo(activity,
|
||||
PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId);
|
||||
} catch (RemoteException e) {
|
||||
// Shouldn't happen.
|
||||
Slog.wtf(TAG, "RemoteException", e);
|
||||
@@ -2454,7 +2533,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
} finally {
|
||||
injectRestoreCallingIdentity(token);
|
||||
|
||||
logDurationStat(Stats.GET_ACTIVITIES_WITH_METADATA, start);
|
||||
logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2531,6 +2610,86 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private Intent getMainActivityIntent() {
|
||||
final Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(LAUNCHER_INTENT_CATEGORY);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) {
|
||||
final long start = injectElapsedRealtime();
|
||||
final long token = injectClearCallingIdentity();
|
||||
try {
|
||||
final Intent intent = getMainActivityIntent();
|
||||
intent.setPackage(packageName);
|
||||
|
||||
final List<ResolveInfo> resolved =
|
||||
mContext.getPackageManager().queryIntentActivitiesAsUser(
|
||||
intent, PACKAGE_MATCH_FLAGS, userId);
|
||||
|
||||
return (resolved == null || resolved.size() == 0)
|
||||
? null : resolved.get(0).activityInfo.getComponentName();
|
||||
} finally {
|
||||
injectRestoreCallingIdentity(token);
|
||||
|
||||
logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start);
|
||||
}
|
||||
}
|
||||
|
||||
boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
|
||||
final long start = injectElapsedRealtime();
|
||||
final long token = injectClearCallingIdentity();
|
||||
try {
|
||||
final Intent intent = getMainActivityIntent();
|
||||
intent.setPackage(activity.getPackageName());
|
||||
intent.setComponent(activity);
|
||||
|
||||
final List<ResolveInfo> resolved =
|
||||
mContext.getPackageManager().queryIntentActivitiesAsUser(
|
||||
intent, PACKAGE_MATCH_FLAGS, userId);
|
||||
|
||||
return resolved != null && resolved.size() > 0;
|
||||
} finally {
|
||||
injectRestoreCallingIdentity(token);
|
||||
|
||||
logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) {
|
||||
final long start = injectElapsedRealtime();
|
||||
final long token = injectClearCallingIdentity();
|
||||
try {
|
||||
final Intent intent = getMainActivityIntent();
|
||||
intent.setPackage(packageName);
|
||||
|
||||
final List<ResolveInfo> resolved =
|
||||
mContext.getPackageManager().queryIntentActivitiesAsUser(
|
||||
intent, PACKAGE_MATCH_FLAGS, userId);
|
||||
|
||||
return (resolved != null) ? resolved : new ArrayList<>(0);
|
||||
} finally {
|
||||
injectRestoreCallingIdentity(token);
|
||||
|
||||
logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
|
||||
}
|
||||
}
|
||||
|
||||
boolean injectIsSafeModeEnabled() {
|
||||
final long token = injectClearCallingIdentity();
|
||||
try {
|
||||
return IWindowManager.Stub
|
||||
.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE))
|
||||
.isSafeModeEnabled();
|
||||
} catch (RemoteException e) {
|
||||
return false; // Shouldn't happen though.
|
||||
} finally {
|
||||
injectRestoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
// === Backup & restore ===
|
||||
|
||||
boolean shouldBackupApp(String packageName, int userId) {
|
||||
@@ -2560,7 +2719,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
|
||||
try {
|
||||
saveUserInternalLocked(userId, os, /* forBackup */ true);
|
||||
} catch (XmlPullParserException|IOException e) {
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
// Shouldn't happen.
|
||||
Slog.w(TAG, "Backup failed.", e);
|
||||
return null;
|
||||
@@ -2579,7 +2738,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
final ByteArrayInputStream is = new ByteArrayInputStream(payload);
|
||||
try {
|
||||
user = loadUserInternal(userId, is, /* fromBackup */ true);
|
||||
} catch (XmlPullParserException|IOException e) {
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
Slog.w(TAG, "Restoration failed.", e);
|
||||
return;
|
||||
}
|
||||
@@ -2656,7 +2815,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
pw.println(mResetInterval);
|
||||
pw.print(" maxUpdatesPerInterval: ");
|
||||
pw.println(mMaxUpdatesPerInterval);
|
||||
pw.print(" maxDynamicShortcuts: ");
|
||||
pw.print(" maxShortcutsPerActivity: ");
|
||||
pw.println(mMaxShortcuts);
|
||||
pw.println();
|
||||
|
||||
@@ -2670,11 +2829,13 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
|
||||
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
|
||||
dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
|
||||
dumpStatLS(pw, p, Stats.GET_ACTIVITIES_WITH_METADATA, "getActivities+metadata");
|
||||
dumpStatLS(pw, p, Stats.GET_ACTIVITY_WITH_METADATA, "getActivity+metadata");
|
||||
dumpStatLS(pw, p, Stats.GET_INSTALLED_PACKAGES, "getInstalledPackages");
|
||||
dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
|
||||
dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources");
|
||||
dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup");
|
||||
dumpStatLS(pw, p, Stats.GET_LAUNCHER_ACTIVITY, "getLauncherActivity");
|
||||
dumpStatLS(pw, p, Stats.CHECK_LAUNCHER_ACTIVITY, "checkLauncherActivity");
|
||||
}
|
||||
|
||||
for (int i = 0; i < mUsers.size(); i++) {
|
||||
@@ -2725,7 +2886,9 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
enforceShell();
|
||||
|
||||
(new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
|
||||
final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
|
||||
|
||||
resultReceiver.send(status, null);
|
||||
}
|
||||
|
||||
static class CommandException extends Exception {
|
||||
@@ -2796,6 +2959,9 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
case "clear-shortcuts":
|
||||
handleClearShortcuts();
|
||||
break;
|
||||
case "verify-states": // hidden command to verify various internal states.
|
||||
handleVerifyStates();
|
||||
break;
|
||||
default:
|
||||
return handleDefaultCommands(cmd);
|
||||
}
|
||||
@@ -2938,7 +3104,16 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName);
|
||||
|
||||
ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId);
|
||||
ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId,
|
||||
/* appStillExists = */ true);
|
||||
}
|
||||
|
||||
private void handleVerifyStates() throws CommandException {
|
||||
try {
|
||||
verifyStatesForce(); // This will throw when there's an issue.
|
||||
} catch (Throwable th) {
|
||||
throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2978,7 +3153,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
|
||||
final void wtf(String message) {
|
||||
wtf( message, /* exception= */ null);
|
||||
wtf(message, /* exception= */ null);
|
||||
}
|
||||
|
||||
// Injection point.
|
||||
@@ -3092,6 +3267,10 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private final void verifyStatesForce() {
|
||||
verifyStatesInner();
|
||||
}
|
||||
|
||||
private void verifyStatesInner() {
|
||||
synchronized (this) {
|
||||
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
|
||||
|
||||
@@ -88,7 +88,7 @@ class ShortcutUser {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{Package: %d, %s}", userId, packageName);
|
||||
return String.format("[Package: %d, %s]", userId, packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +99,6 @@ class ShortcutUser {
|
||||
|
||||
private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
|
||||
|
||||
private final SparseArray<ShortcutPackage> mPackagesFromUid = new SparseArray<>();
|
||||
|
||||
private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
|
||||
|
||||
/** Default launcher that can access the launcher apps APIs. */
|
||||
@@ -244,12 +242,12 @@ class ShortcutUser {
|
||||
}
|
||||
}
|
||||
|
||||
public void handlePackageAddedOrUpdated(@NonNull String packageName) {
|
||||
public void handlePackageAddedOrUpdated(@NonNull String packageName, boolean forceRescan) {
|
||||
final boolean isNewApp = !mPackages.containsKey(packageName);
|
||||
|
||||
final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
|
||||
|
||||
if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) {
|
||||
if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp, forceRescan)) {
|
||||
if (isNewApp) {
|
||||
mPackages.remove(packageName);
|
||||
}
|
||||
@@ -381,8 +379,10 @@ class ShortcutUser {
|
||||
pw.print(mUserId);
|
||||
pw.print(" Known locale seq#: ");
|
||||
pw.print(mKnownLocaleChangeSequenceNumber);
|
||||
pw.print(" Last app scan: ");
|
||||
pw.print(" Last app scan: [");
|
||||
pw.print(mLastAppScanTime);
|
||||
pw.print("] ");
|
||||
pw.print(ShortcutService.formatTime(mLastAppScanTime));
|
||||
pw.println();
|
||||
|
||||
prefix += prefix + " ";
|
||||
|
||||
@@ -52,6 +52,7 @@ import android.content.pm.LauncherApps.ShortcutQuery;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.content.pm.ShortcutServiceInternal;
|
||||
@@ -98,8 +99,11 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
protected static final String TAG = "ShortcutManagerTest";
|
||||
@@ -114,6 +118,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
|
||||
protected static final String[] EMPTY_STRINGS = new String[0]; // Just for readability.
|
||||
|
||||
protected static final String MAIN_ACTIVITY_CLASS = "MainActivity";
|
||||
|
||||
// public for mockito
|
||||
public class BaseContext extends MockContext {
|
||||
@Override
|
||||
@@ -311,8 +317,55 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
|
||||
return mContext.injectGetActivitiesWithMetadata(packageName, userId);
|
||||
ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity,
|
||||
@UserIdInt int userId) {
|
||||
final PackageInfo pi = mContext.injectGetActivitiesWithMetadata(
|
||||
activity.getPackageName(), userId);
|
||||
if (pi == null || pi.activities == null) {
|
||||
return null;
|
||||
}
|
||||
for (ActivityInfo ai : pi.activities) {
|
||||
if (!mEnabledActivityChecker.test(ai.getComponentName(), userId)) {
|
||||
continue;
|
||||
}
|
||||
if (activity.equals(ai.getComponentName())) {
|
||||
return ai;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
|
||||
if (!mEnabledActivityChecker.test(activity, userId)) {
|
||||
return false;
|
||||
}
|
||||
return mMainActivityChecker.test(activity, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) {
|
||||
final PackageInfo pi = mContext.injectGetActivitiesWithMetadata(
|
||||
packageName, userId);
|
||||
if (pi == null || pi.activities == null) {
|
||||
return null;
|
||||
}
|
||||
final ArrayList<ResolveInfo> ret = new ArrayList<>(pi.activities.length);
|
||||
for (int i = 0; i < pi.activities.length; i++) {
|
||||
if (!mEnabledActivityChecker.test(pi.activities[i].getComponentName(), userId)) {
|
||||
continue;
|
||||
}
|
||||
final ResolveInfo ri = new ResolveInfo();
|
||||
ri.activityInfo = pi.activities[i];
|
||||
ret.add(ri);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) {
|
||||
return mMainActivityFetcher.apply(packageName, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -334,6 +387,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean injectIsSafeModeEnabled() {
|
||||
return mSafeMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
void wtf(String message, Exception e) {
|
||||
// During tests, WTF is fatal.
|
||||
@@ -441,6 +499,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
|
||||
protected File mInjectedFilePathRoot;
|
||||
|
||||
protected boolean mSafeMode;
|
||||
|
||||
protected long mInjectedCurrentTimeMillis;
|
||||
|
||||
protected boolean mInjectedIsLowRamDevice;
|
||||
@@ -512,6 +572,15 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
|
||||
|| LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage);
|
||||
|
||||
protected BiPredicate<ComponentName, Integer> mMainActivityChecker =
|
||||
(activity, userId) -> true;
|
||||
|
||||
protected BiFunction<String, Integer, ComponentName> mMainActivityFetcher =
|
||||
(packageName, userId) -> new ComponentName(packageName, MAIN_ACTIVITY_CLASS);
|
||||
|
||||
protected BiPredicate<ComponentName, Integer> mEnabledActivityChecker
|
||||
= (activity, userId) -> true; // all activities are enabled.
|
||||
|
||||
protected static final long START_TIME = 1440000000101L;
|
||||
|
||||
protected static final long INTERVAL = 10000;
|
||||
@@ -1051,10 +1120,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
+ "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
|
||||
}
|
||||
|
||||
protected void waitOnMainThread() throws Throwable {
|
||||
runTestOnUiThread(() -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a shortcut with an ID.
|
||||
*/
|
||||
@@ -1410,6 +1475,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||
return i;
|
||||
}
|
||||
|
||||
protected Intent genPackageChangedIntent(String pakcageName, int userId) {
|
||||
Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED);
|
||||
i.setData(Uri.parse("package:" + pakcageName));
|
||||
i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
|
||||
return i;
|
||||
}
|
||||
|
||||
protected Intent genPackageDataClear(String packageName, int userId) {
|
||||
Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
|
||||
i.setData(Uri.parse("package:" + packageName));
|
||||
|
||||
@@ -40,6 +40,7 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
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.assertForLauncherCallback;
|
||||
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;
|
||||
@@ -50,6 +51,7 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
|
||||
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;
|
||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.waitOnMainThread;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
@@ -84,6 +86,7 @@ import com.android.frameworks.servicestests.R;
|
||||
import com.android.server.pm.ShortcutService.ConfigConstants;
|
||||
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
|
||||
import com.android.server.pm.ShortcutUser.PackageWithUser;
|
||||
import com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.ShortcutListAsserter;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
@@ -91,6 +94,9 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Tests for ShortcutService and ShortcutManager.
|
||||
@@ -347,6 +353,127 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
});
|
||||
}
|
||||
|
||||
public void testPublishWithNoActivity() {
|
||||
// If activity is not explicitly set, use the default one.
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
|
||||
// s1 and s3 has no activities.
|
||||
final ShortcutInfo si1 = new ShortcutInfo.Builder(mClientContext, "si1")
|
||||
.setShortLabel("label1")
|
||||
.setIntent(new Intent("action1"))
|
||||
.build();
|
||||
final ShortcutInfo si2 = new ShortcutInfo.Builder(mClientContext, "si2")
|
||||
.setShortLabel("label2")
|
||||
.setActivity(new ComponentName(getCallingPackage(), "abc"))
|
||||
.setIntent(new Intent("action2"))
|
||||
.build();
|
||||
final ShortcutInfo si3 = new ShortcutInfo.Builder(mClientContext, "si3")
|
||||
.setShortLabel("label3")
|
||||
.setIntent(new Intent("action3"))
|
||||
.build();
|
||||
|
||||
// Set test 1
|
||||
assertTrue(mManager.setDynamicShortcuts(list(si1)));
|
||||
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("si1")
|
||||
.forShortcutWithId("si1", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
MAIN_ACTIVITY_CLASS), si.getActivity());
|
||||
});
|
||||
|
||||
// Set test 2
|
||||
assertTrue(mManager.setDynamicShortcuts(list(si2, si1)));
|
||||
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("si1", "si2")
|
||||
.forShortcutWithId("si1", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
MAIN_ACTIVITY_CLASS), si.getActivity());
|
||||
})
|
||||
.forShortcutWithId("si2", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
"abc"), si.getActivity());
|
||||
});
|
||||
|
||||
|
||||
// Set test 3
|
||||
assertTrue(mManager.setDynamicShortcuts(list(si3, si1)));
|
||||
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("si1", "si3")
|
||||
.forShortcutWithId("si1", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
MAIN_ACTIVITY_CLASS), si.getActivity());
|
||||
})
|
||||
.forShortcutWithId("si3", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
MAIN_ACTIVITY_CLASS), si.getActivity());
|
||||
});
|
||||
|
||||
mInjectedCurrentTimeMillis += INTERVAL; // reset throttling
|
||||
|
||||
// Add test 1
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
assertTrue(mManager.addDynamicShortcuts(list(si1)));
|
||||
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("si1")
|
||||
.forShortcutWithId("si1", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
MAIN_ACTIVITY_CLASS), si.getActivity());
|
||||
});
|
||||
|
||||
// Add test 2
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
assertTrue(mManager.addDynamicShortcuts(list(si2, si1)));
|
||||
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("si1", "si2")
|
||||
.forShortcutWithId("si1", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
MAIN_ACTIVITY_CLASS), si.getActivity());
|
||||
})
|
||||
.forShortcutWithId("si2", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
"abc"), si.getActivity());
|
||||
});
|
||||
|
||||
|
||||
// Add test 3
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
assertTrue(mManager.addDynamicShortcuts(list(si3, si1)));
|
||||
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("si1", "si3")
|
||||
.forShortcutWithId("si1", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
MAIN_ACTIVITY_CLASS), si.getActivity());
|
||||
})
|
||||
.forShortcutWithId("si3", si -> {
|
||||
assertEquals(new ComponentName(getCallingPackage(),
|
||||
MAIN_ACTIVITY_CLASS), si.getActivity());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void testPublishWithNoActivity_noMainActivityInPackage() {
|
||||
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
|
||||
final ShortcutInfo si1 = new ShortcutInfo.Builder(mClientContext, "si1")
|
||||
.setShortLabel("label1")
|
||||
.setIntent(new Intent("action1"))
|
||||
.build();
|
||||
|
||||
// Returning null means there's no main activity in this package.
|
||||
mMainActivityFetcher = (packageName, userId) -> null;
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class, "Launcher activity not found for", () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(si1)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void testDeleteDynamicShortcuts() {
|
||||
final ShortcutInfo si1 = makeShortcut("shortcut1");
|
||||
final ShortcutInfo si2 = makeShortcut("shortcut2");
|
||||
@@ -2336,151 +2463,87 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
+ ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
|
||||
);
|
||||
|
||||
LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
|
||||
setCaller(LAUNCHER_1, USER_0);
|
||||
|
||||
// Set listeners
|
||||
|
||||
runWithCaller(LAUNCHER_1, USER_0, () -> {
|
||||
mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper()));
|
||||
});
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||
});
|
||||
|
||||
waitOnMainThread();
|
||||
ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_1),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||
});
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||
.haveIds("s1", "s2", "s3")
|
||||
.areAllWithKeyFieldsOnly()
|
||||
.areAllDynamic();
|
||||
|
||||
// From different package.
|
||||
reset(c0);
|
||||
runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||
});
|
||||
waitOnMainThread();
|
||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_2),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||
});
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_2, HANDLE_USER_0)
|
||||
.haveIds("s1", "s2", "s3")
|
||||
.areAllWithKeyFieldsOnly()
|
||||
.areAllDynamic();
|
||||
|
||||
// Different user, callback shouldn't be called.
|
||||
reset(c0);
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||
});
|
||||
waitOnMainThread();
|
||||
verify(c0, times(0)).onShortcutsChanged(
|
||||
anyString(),
|
||||
any(List.class),
|
||||
any(UserHandle.class)
|
||||
);
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||
});
|
||||
}).assertNoCallbackCalled();
|
||||
|
||||
|
||||
// Test for addDynamicShortcuts.
|
||||
reset(c0);
|
||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
||||
dumpsysOnLogcat("before addDynamicShortcuts");
|
||||
assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
|
||||
});
|
||||
|
||||
waitOnMainThread();
|
||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_1),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
|
||||
});
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||
.haveIds("s1", "s2", "s3", "s4")
|
||||
.areAllWithKeyFieldsOnly()
|
||||
.areAllDynamic();
|
||||
|
||||
// Test for remove
|
||||
reset(c0);
|
||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
||||
mManager.removeDynamicShortcuts(list("s1"));
|
||||
});
|
||||
|
||||
waitOnMainThread();
|
||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_1),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
mManager.removeDynamicShortcuts(list("s1"));
|
||||
});
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||
.haveIds("s2", "s3", "s4")
|
||||
.areAllWithKeyFieldsOnly()
|
||||
.areAllDynamic();
|
||||
|
||||
// Test for update
|
||||
reset(c0);
|
||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
||||
assertTrue(mManager.updateShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"))));
|
||||
});
|
||||
|
||||
waitOnMainThread();
|
||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_1),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertTrue(mManager.updateShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"))));
|
||||
});
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||
// All remaining shortcuts will be passed regardless of what's been updated.
|
||||
.haveIds("s2", "s3", "s4")
|
||||
.areAllWithKeyFieldsOnly()
|
||||
.areAllDynamic();
|
||||
|
||||
// Test for deleteAll
|
||||
reset(c0);
|
||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
});
|
||||
|
||||
waitOnMainThread();
|
||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_1),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
mManager.removeAllDynamicShortcuts();
|
||||
});
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||
.isEmpty();
|
||||
|
||||
// Update package1 with manifest shortcuts
|
||||
reset(c0);
|
||||
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));
|
||||
|
||||
waitOnMainThread();
|
||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_1),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
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));
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||
.areAllManifest()
|
||||
.areAllWithKeyFieldsOnly()
|
||||
.haveIds("ms1", "ms2");
|
||||
@@ -2518,58 +2581,42 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||
|
||||
reset(c0); // Check the callback for the next API call.
|
||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
||||
mManager.removeDynamicShortcuts(list("s2"));
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
mManager.removeDynamicShortcuts(list("s2"));
|
||||
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms2", "s1", "s2")
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms2", "s1", "s2")
|
||||
|
||||
.selectByIds("ms2")
|
||||
.areAllNotManifest()
|
||||
.areAllPinned()
|
||||
.areAllImmutable()
|
||||
.areAllDisabled()
|
||||
.selectByIds("ms2")
|
||||
.areAllNotManifest()
|
||||
.areAllPinned()
|
||||
.areAllImmutable()
|
||||
.areAllDisabled()
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("s1")
|
||||
.areAllDynamic()
|
||||
.areAllNotPinned()
|
||||
.areAllEnabled()
|
||||
.revertToOriginalList()
|
||||
.selectByIds("s1")
|
||||
.areAllDynamic()
|
||||
.areAllNotPinned()
|
||||
.areAllEnabled()
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("s2")
|
||||
.areAllNotDynamic()
|
||||
.areAllPinned()
|
||||
.areAllEnabled()
|
||||
;
|
||||
});
|
||||
|
||||
waitOnMainThread();
|
||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_1),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
.revertToOriginalList()
|
||||
.selectByIds("s2")
|
||||
.areAllNotDynamic()
|
||||
.areAllPinned()
|
||||
.areAllEnabled()
|
||||
;
|
||||
});
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||
.haveIds("ms2", "s1", "s2")
|
||||
.areAllWithKeyFieldsOnly();
|
||||
|
||||
// Remove CALLING_PACKAGE_2
|
||||
reset(c0);
|
||||
uninstallPackage(USER_0, CALLING_PACKAGE_2);
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
|
||||
|
||||
// Should get a callback with an empty list.
|
||||
waitOnMainThread();
|
||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(c0).onShortcutsChanged(
|
||||
eq(CALLING_PACKAGE_2),
|
||||
shortcuts.capture(),
|
||||
eq(HANDLE_USER_0)
|
||||
);
|
||||
assertWith(shortcuts.getValue())
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
uninstallPackage(USER_0, CALLING_PACKAGE_2);
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0,
|
||||
/* appStillExists = */ false);
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_2, HANDLE_USER_0)
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@@ -2970,7 +3017,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
|
||||
// Nonexistent package.
|
||||
uninstallPackage(USER_0, "abc");
|
||||
mService.cleanUpPackageLocked("abc", USER_0, USER_0);
|
||||
mService.cleanUpPackageLocked("abc", USER_0, USER_0, /* appStillExists = */ false);
|
||||
|
||||
// No changes.
|
||||
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
|
||||
@@ -3002,7 +3049,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
|
||||
// Remove a package.
|
||||
uninstallPackage(USER_0, CALLING_PACKAGE_1);
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0,
|
||||
/* appStillExists = */ false);
|
||||
|
||||
assertEquals(set(CALLING_PACKAGE_2),
|
||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||
@@ -3033,7 +3081,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
|
||||
// Remove a launcher.
|
||||
uninstallPackage(USER_10, LAUNCHER_1);
|
||||
mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
|
||||
mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10, /* appStillExists = */ false);
|
||||
|
||||
assertEquals(set(CALLING_PACKAGE_2),
|
||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||
@@ -3061,7 +3109,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
|
||||
// Remove a package.
|
||||
uninstallPackage(USER_10, CALLING_PACKAGE_2);
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10,
|
||||
/* appStillExists = */ false);
|
||||
|
||||
assertEquals(set(CALLING_PACKAGE_2),
|
||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||
@@ -3089,7 +3138,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
|
||||
// Remove the other launcher from user 10 too.
|
||||
uninstallPackage(USER_10, LAUNCHER_2);
|
||||
mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
|
||||
mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10,
|
||||
/* appStillExists = */ false);
|
||||
|
||||
assertEquals(set(CALLING_PACKAGE_2),
|
||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||
@@ -3117,7 +3167,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
|
||||
// More remove.
|
||||
uninstallPackage(USER_10, CALLING_PACKAGE_1);
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10,
|
||||
/* appStillExists = */ false);
|
||||
|
||||
assertEquals(set(CALLING_PACKAGE_2),
|
||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||
@@ -3143,6 +3194,74 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
mService.saveDirtyInfo();
|
||||
}
|
||||
|
||||
public void testCleanupPackage_republishManifests() {
|
||||
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));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||
});
|
||||
runWithCaller(LAUNCHER_1, USER_0, () -> {
|
||||
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
|
||||
list("s2", "s3", "ms1", "ms2"), HANDLE_USER_0);
|
||||
});
|
||||
|
||||
// Remove ms2 from manifest.
|
||||
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));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"))));
|
||||
|
||||
// Make sure the shortcuts are in the intended state.
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1", "ms2", "s1", "s2", "s3")
|
||||
|
||||
.selectByIds("ms1")
|
||||
.areAllManifest()
|
||||
.areAllPinned()
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms2")
|
||||
.areAllNotManifest()
|
||||
.areAllPinned()
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("s1")
|
||||
.areAllDynamic()
|
||||
.areAllNotPinned()
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("s2")
|
||||
.areAllDynamic()
|
||||
.areAllPinned()
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("s3")
|
||||
.areAllNotDynamic()
|
||||
.areAllPinned();
|
||||
});
|
||||
|
||||
// Clean up + re-publish manifests.
|
||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0,
|
||||
/* appStillExists = */ true);
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1")
|
||||
.areAllManifest();
|
||||
});
|
||||
}
|
||||
|
||||
public void testHandleGonePackage_crossProfile() {
|
||||
// Create some shortcuts.
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
@@ -3454,6 +3573,20 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
assertTrue(mManager.addDynamicShortcuts(list(
|
||||
makeShortcutWithIcon("s1", bmp32x32), makeShortcutWithIcon("s2", bmp32x32)
|
||||
)));
|
||||
// Also add a manifest shortcut, which should be removed too.
|
||||
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));
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("s1", "s2", "ms1")
|
||||
|
||||
.selectManifest()
|
||||
.haveIds("ms1");
|
||||
});
|
||||
|
||||
setCaller(CALLING_PACKAGE_2, USER_0);
|
||||
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
|
||||
@@ -3629,8 +3762,47 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
|
||||
}
|
||||
|
||||
public void testHandlePackageUpdate() throws Throwable {
|
||||
public void testHandlePackageClearData_manifestRepublished() {
|
||||
|
||||
// Add two manifests and two dynamics.
|
||||
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_10));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertTrue(mManager.addDynamicShortcuts(list(
|
||||
makeShortcut("s1"), makeShortcut("s2"))));
|
||||
});
|
||||
runWithCaller(LAUNCHER_1, USER_10, () -> {
|
||||
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2", "s2"), HANDLE_USER_10);
|
||||
});
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1", "ms2", "s1", "s2")
|
||||
.areAllEnabled()
|
||||
|
||||
.selectPinned()
|
||||
.haveIds("ms2", "s2");
|
||||
});
|
||||
|
||||
// Clear data
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageDataClear(CALLING_PACKAGE_1, USER_10));
|
||||
|
||||
// Only manifest shortcuts will remain, and are no longer pinned.
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1", "ms2")
|
||||
.areAllEnabled()
|
||||
.areAllNotPinned();
|
||||
});
|
||||
}
|
||||
|
||||
public void testHandlePackageUpdate() throws Throwable {
|
||||
// Set up shortcuts and launchers.
|
||||
|
||||
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
|
||||
@@ -3894,6 +4066,202 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
||||
});
|
||||
}
|
||||
|
||||
public void testHandlePackageChanged() {
|
||||
final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1");
|
||||
final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2");
|
||||
|
||||
addManifestShortcutResource(ACTIVITY1, R.xml.shortcut_1);
|
||||
addManifestShortcutResource(ACTIVITY2, R.xml.shortcut_1_alt);
|
||||
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertTrue(mManager.addDynamicShortcuts(list(
|
||||
makeShortcutWithActivity("s1", ACTIVITY1),
|
||||
makeShortcutWithActivity("s2", ACTIVITY2)
|
||||
)));
|
||||
});
|
||||
runWithCaller(LAUNCHER_1, USER_10, () -> {
|
||||
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1-alt", "s2"), HANDLE_USER_10);
|
||||
});
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1", "ms1-alt", "s1", "s2")
|
||||
.areAllEnabled()
|
||||
|
||||
.selectPinned()
|
||||
.haveIds("ms1-alt", "s2")
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms1", "s1")
|
||||
.areAllWithActivity(ACTIVITY1)
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms1-alt", "s2")
|
||||
.areAllWithActivity(ACTIVITY2)
|
||||
;
|
||||
});
|
||||
|
||||
// First, no changes.
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1", "ms1-alt", "s1", "s2")
|
||||
.areAllEnabled()
|
||||
|
||||
.selectPinned()
|
||||
.haveIds("ms1-alt", "s2")
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms1", "s1")
|
||||
.areAllWithActivity(ACTIVITY1)
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms1-alt", "s2")
|
||||
.areAllWithActivity(ACTIVITY2)
|
||||
;
|
||||
});
|
||||
|
||||
// Disable activity 1
|
||||
mEnabledActivityChecker = (activity, userId) -> !ACTIVITY1.equals(activity);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1-alt", "s2")
|
||||
.areAllEnabled()
|
||||
|
||||
.selectPinned()
|
||||
.haveIds("ms1-alt", "s2")
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms1-alt", "s2")
|
||||
.areAllWithActivity(ACTIVITY2)
|
||||
;
|
||||
});
|
||||
|
||||
// Re-enable activity 1.
|
||||
// Manifest shortcuts will be re-published, but dynamic ones are not.
|
||||
mEnabledActivityChecker = (activity, userId) -> true;
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1", "ms1-alt", "s2")
|
||||
.areAllEnabled()
|
||||
|
||||
.selectPinned()
|
||||
.haveIds("ms1-alt", "s2")
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms1")
|
||||
.areAllWithActivity(ACTIVITY1)
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms1-alt", "s2")
|
||||
.areAllWithActivity(ACTIVITY2)
|
||||
;
|
||||
});
|
||||
|
||||
// Disable activity 2
|
||||
// Because "ms1-alt" and "s2" are both pinned, they will remain, but disabled.
|
||||
mEnabledActivityChecker = (activity, userId) -> !ACTIVITY2.equals(activity);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("ms1", "ms1-alt", "s2")
|
||||
|
||||
.selectDynamic().isEmpty().revertToOriginalList() // no dynamics.
|
||||
|
||||
.selectPinned()
|
||||
.haveIds("ms1-alt", "s2")
|
||||
.areAllDisabled()
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("ms1")
|
||||
.areAllWithActivity(ACTIVITY1)
|
||||
.areAllEnabled()
|
||||
;
|
||||
});
|
||||
}
|
||||
|
||||
public void testHandlePackageUpdate_activityNoLongerMain() throws Throwable {
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertTrue(mManager.setDynamicShortcuts(list(
|
||||
makeShortcutWithActivity("s1a",
|
||||
new ComponentName(getCallingPackage(), "act1")),
|
||||
makeShortcutWithActivity("s1b",
|
||||
new ComponentName(getCallingPackage(), "act1")),
|
||||
makeShortcutWithActivity("s2a",
|
||||
new ComponentName(getCallingPackage(), "act2")),
|
||||
makeShortcutWithActivity("s2b",
|
||||
new ComponentName(getCallingPackage(), "act2")),
|
||||
makeShortcutWithActivity("s3a",
|
||||
new ComponentName(getCallingPackage(), "act3")),
|
||||
makeShortcutWithActivity("s3b",
|
||||
new ComponentName(getCallingPackage(), "act3"))
|
||||
)));
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("s1a", "s1b", "s2a", "s2b", "s3a", "s3b")
|
||||
.areAllDynamic();
|
||||
});
|
||||
runWithCaller(LAUNCHER_1, USER_0, () -> {
|
||||
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
|
||||
list("s1b", "s2b", "s3b"),
|
||||
HANDLE_USER_0);
|
||||
});
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("s1a", "s1b", "s2a", "s2b", "s3a", "s3b")
|
||||
.areAllDynamic()
|
||||
|
||||
.selectByIds("s1b", "s2b", "s3b")
|
||||
.areAllPinned();
|
||||
});
|
||||
|
||||
// Update the app and act2 and act3 are no longer main.
|
||||
mMainActivityChecker = (activity, userId) -> {
|
||||
return activity.getClassName().equals("act1");
|
||||
};
|
||||
|
||||
setCaller(LAUNCHER_1, USER_0);
|
||||
assertForLauncherCallback(mLauncherApps, () -> {
|
||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||
genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
|
||||
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||
// Make sure the launcher gets callbacks.
|
||||
.haveIds("s1a", "s1b", "s2b", "s3b")
|
||||
.areAllWithKeyFieldsOnly();
|
||||
|
||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||
// s2a and s3a are gone, but s2b and s3b will remain because they're pinned, and
|
||||
// disabled.
|
||||
assertWith(getCallerShortcuts())
|
||||
.haveIds("s1a", "s1b", "s2b", "s3b")
|
||||
|
||||
.selectByIds("s1a", "s1b")
|
||||
.areAllDynamic()
|
||||
.areAllEnabled()
|
||||
|
||||
.revertToOriginalList()
|
||||
.selectByIds("s2b", "s3b")
|
||||
.areAllNotDynamic()
|
||||
.areAllDisabled()
|
||||
.areAllPinned()
|
||||
;
|
||||
});
|
||||
}
|
||||
|
||||
protected void prepareForBackupTest() {
|
||||
|
||||
prepareCrossProfileDataSet();
|
||||
|
||||
@@ -72,18 +72,68 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
|
||||
"ID must be provided",
|
||||
() -> new ShortcutInfo.Builder(getTestContext()).build());
|
||||
|
||||
assertExpectException(NullPointerException.class, "Intent action must be set",
|
||||
() -> new ShortcutInfo.Builder(getTestContext()).setIntent(new Intent()));
|
||||
|
||||
assertExpectException(NullPointerException.class, "Activity must be provided", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext()).setId("id").build();
|
||||
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
||||
});
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"id cannot be empty",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), null));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"id cannot be empty",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), ""));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"intent cannot be null",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(null));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"action must be set",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"activity cannot be null",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setActivity(null));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"shortLabel cannot be empty",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(null));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"shortLabel cannot be empty",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(""));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"longLabel cannot be empty",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(null));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"longLabel cannot be empty",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(""));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"disabledMessage cannot be empty",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(null));
|
||||
|
||||
assertExpectException(
|
||||
RuntimeException.class,
|
||||
"disabledMessage cannot be empty",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(""));
|
||||
|
||||
assertExpectException(NullPointerException.class, "action must be set",
|
||||
() -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
|
||||
|
||||
// same for add.
|
||||
assertExpectException(
|
||||
IllegalArgumentException.class, "Short label must be provided", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
||||
.setId("id")
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
||||
.build();
|
||||
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
||||
@@ -91,25 +141,24 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
|
||||
|
||||
assertExpectException(
|
||||
IllegalArgumentException.class, "Short label must be provided", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
||||
.setId("id")
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
||||
.build();
|
||||
assertTrue(getManager().addDynamicShortcuts(list(si)));
|
||||
});
|
||||
|
||||
// same for add.
|
||||
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
||||
.setId("id")
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
||||
.setShortLabel("x")
|
||||
.build();
|
||||
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
||||
});
|
||||
|
||||
// same for add.
|
||||
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
||||
.setId("id")
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
||||
.setShortLabel("x")
|
||||
.build();
|
||||
@@ -117,18 +166,17 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
|
||||
});
|
||||
|
||||
assertExpectException(
|
||||
IllegalStateException.class, "package name mismatch", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
||||
.setId("id")
|
||||
IllegalStateException.class, "does not belong to package", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||
.setActivity(new ComponentName("xxx", "s"))
|
||||
.build();
|
||||
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
||||
});
|
||||
|
||||
// same for add.
|
||||
assertExpectException(
|
||||
IllegalStateException.class, "package name mismatch", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
||||
.setId("id")
|
||||
IllegalStateException.class, "does not belong to package", () -> {
|
||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||
.setActivity(new ComponentName("xxx", "s"))
|
||||
.build();
|
||||
assertTrue(getManager().addDynamicShortcuts(list(si)));
|
||||
|
||||
@@ -24,9 +24,11 @@ import static junit.framework.Assert.assertTrue;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyList;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -35,11 +37,14 @@ import android.app.Instrumentation;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.LauncherApps.Callback;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.BaseBundle;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.PersistableBundle;
|
||||
@@ -54,6 +59,7 @@ import junit.framework.AssertionFailedError;
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -69,6 +75,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -664,8 +671,8 @@ public class ShortcutManagerTestUtils {
|
||||
}
|
||||
|
||||
private ShortcutListAsserter(ShortcutListAsserter original, List<ShortcutInfo> list) {
|
||||
mOriginal = original == null ? this : original;
|
||||
mList = new ArrayList<>(list);
|
||||
mOriginal = (original == null) ? this : original;
|
||||
mList = (list == null) ? new ArrayList<>(0) : new ArrayList<>(list);
|
||||
}
|
||||
|
||||
public ShortcutListAsserter revertToOriginalList() {
|
||||
@@ -813,6 +820,11 @@ public class ShortcutManagerTestUtils {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShortcutListAsserter areAllWithActivity(ComponentName activity) {
|
||||
forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.getActivity().equals(activity)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
|
||||
boolean found = false;
|
||||
for (int i = 0; i < mList.size(); i++) {
|
||||
@@ -902,4 +914,69 @@ public class ShortcutManagerTestUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void waitOnMainThread() throws InterruptedException {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(() -> latch.countDown());
|
||||
|
||||
latch.await();
|
||||
}
|
||||
|
||||
public static class LauncherCallbackAsserter {
|
||||
private final LauncherApps.Callback mCallback = mock(LauncherApps.Callback.class);
|
||||
|
||||
private Callback getMockCallback() {
|
||||
return mCallback;
|
||||
}
|
||||
|
||||
public LauncherCallbackAsserter assertNoCallbackCalled() {
|
||||
verify(mCallback, times(0)).onShortcutsChanged(
|
||||
anyString(),
|
||||
any(List.class),
|
||||
any(UserHandle.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
public LauncherCallbackAsserter assertNoCallbackCalledForPackage(
|
||||
String publisherPackageName) {
|
||||
verify(mCallback, times(0)).onShortcutsChanged(
|
||||
eq(publisherPackageName),
|
||||
any(List.class),
|
||||
any(UserHandle.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
public LauncherCallbackAsserter assertNoCallbackCalledForPackageAndUser(
|
||||
String publisherPackageName, UserHandle publisherUserHandle) {
|
||||
verify(mCallback, times(0)).onShortcutsChanged(
|
||||
eq(publisherPackageName),
|
||||
any(List.class),
|
||||
eq(publisherUserHandle));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShortcutListAsserter assertCallbackCalledForPackageAndUser(
|
||||
String publisherPackageName, UserHandle publisherUserHandle) {
|
||||
final ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class);
|
||||
verify(mCallback, times(1)).onShortcutsChanged(
|
||||
eq(publisherPackageName),
|
||||
shortcuts.capture(),
|
||||
eq(publisherUserHandle));
|
||||
return new ShortcutListAsserter(shortcuts.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public static LauncherCallbackAsserter assertForLauncherCallback(
|
||||
LauncherApps launcherApps, Runnable body) throws InterruptedException {
|
||||
final LauncherCallbackAsserter asserter = new LauncherCallbackAsserter();
|
||||
launcherApps.registerCallback(asserter.getMockCallback(),
|
||||
new Handler(Looper.getMainLooper()));
|
||||
|
||||
body.run();
|
||||
|
||||
waitOnMainThread();
|
||||
|
||||
return asserter;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user