Merge "Shortcut: Only "main" activities can have shortcuts." into nyc-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
1af9a58956
@@ -709,7 +709,7 @@ public final class ShortcutInfo implements Parcelable {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Builder setId(@NonNull String id) {
|
public Builder setId(@NonNull String id) {
|
||||||
mId = Preconditions.checkStringNotEmpty(id, "id");
|
mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,14 +721,17 @@ public final class ShortcutInfo implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
public Builder(Context context, String id) {
|
public Builder(Context context, String id) {
|
||||||
mContext = context;
|
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.
|
* 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
|
* <p>Only "main" activities -- i.e. ones with an intent filter for
|
||||||
* {@link ShortcutManager#updateShortcuts(List)}.
|
* {@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
|
* <p>The package name of the target activity must match the package name of the shortcut
|
||||||
* publisher.
|
* publisher.
|
||||||
@@ -738,7 +741,7 @@ public final class ShortcutInfo implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public Builder setActivity(@NonNull ComponentName activity) {
|
public Builder setActivity(@NonNull ComponentName activity) {
|
||||||
mActivity = Preconditions.checkNotNull(activity, "activity");
|
mActivity = Preconditions.checkNotNull(activity, "activity cannot be null");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,7 +788,7 @@ public final class ShortcutInfo implements Parcelable {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public Builder setShortLabel(@NonNull CharSequence shortLabel) {
|
public Builder setShortLabel(@NonNull CharSequence shortLabel) {
|
||||||
Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set");
|
Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set");
|
||||||
mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel");
|
mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -810,7 +813,7 @@ public final class ShortcutInfo implements Parcelable {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public Builder setLongLabel(@NonNull CharSequence longLabel) {
|
public Builder setLongLabel(@NonNull CharSequence longLabel) {
|
||||||
Preconditions.checkState(mTextResId == 0, "longLabelResId already set");
|
Preconditions.checkState(mTextResId == 0, "longLabelResId already set");
|
||||||
mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel");
|
mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,7 +857,8 @@ public final class ShortcutInfo implements Parcelable {
|
|||||||
Preconditions.checkState(
|
Preconditions.checkState(
|
||||||
mDisabledMessageResId == 0, "disabledMessageResId already set");
|
mDisabledMessageResId == 0, "disabledMessageResId already set");
|
||||||
mDisabledMessage =
|
mDisabledMessage =
|
||||||
Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage");
|
Preconditions.checkStringNotEmpty(disabledMessage,
|
||||||
|
"disabledMessage cannot be empty");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,8 +880,8 @@ public final class ShortcutInfo implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public Builder setIntent(@NonNull Intent intent) {
|
public Builder setIntent(@NonNull Intent intent) {
|
||||||
mIntent = Preconditions.checkNotNull(intent, "intent");
|
mIntent = Preconditions.checkNotNull(intent, "intent cannot be null");
|
||||||
Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set");
|
Preconditions.checkNotNull(mIntent.getAction(), "intent's action must be set");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -944,6 +948,11 @@ public final class ShortcutInfo implements Parcelable {
|
|||||||
return mActivity;
|
return mActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public void setActivity(ComponentName activity) {
|
||||||
|
mActivity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Icon.
|
* Icon.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -54,8 +54,6 @@ import java.util.function.Predicate;
|
|||||||
* User information used by {@link ShortcutService}.
|
* User information used by {@link ShortcutService}.
|
||||||
*
|
*
|
||||||
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
|
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
|
||||||
*
|
|
||||||
* TODO Max dynamic shortcuts cap should be per activity.
|
|
||||||
*/
|
*/
|
||||||
class ShortcutPackage extends ShortcutPackageItem {
|
class ShortcutPackage extends ShortcutPackageItem {
|
||||||
private static final String TAG = ShortcutService.TAG;
|
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
|
* 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.
|
* 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) {
|
public boolean deleteDynamicWithId(@NonNull String shortcutId) {
|
||||||
deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
|
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.
|
* @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(
|
final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
|
||||||
getPackageName(), getPackageUserId());
|
getPackageName(), getPackageUserId());
|
||||||
if (pi == null) {
|
if (pi == null) {
|
||||||
return false; // Shouldn't happen.
|
return false; // Shouldn't happen.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNewApp) {
|
if (!isNewApp && !forceRescan) {
|
||||||
// Make sure the version code or last update time has changed.
|
// Make sure the version code or last update time has changed.
|
||||||
// Otherwise, nothing to do.
|
// Otherwise, nothing to do.
|
||||||
if (getPackageInfo().getVersionCode() >= pi.versionCode
|
if (getPackageInfo().getVersionCode() >= pi.versionCode
|
||||||
@@ -649,12 +665,26 @@ class ShortcutPackage extends ShortcutPackageItem {
|
|||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
|
|
||||||
// For existing shortcuts, update timestamps if they have any resources.
|
// For existing shortcuts, update timestamps if they have any resources.
|
||||||
|
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
|
||||||
if (!isNewApp) {
|
if (!isNewApp) {
|
||||||
Resources publisherRes = null;
|
Resources publisherRes = null;
|
||||||
|
|
||||||
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
|
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
|
||||||
final ShortcutInfo si = mShortcuts.valueAt(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.hasAnyResources()) {
|
||||||
if (!si.isOriginallyFromManifest()) {
|
if (!si.isOriginallyFromManifest()) {
|
||||||
if (publisherRes == null) {
|
if (publisherRes == null) {
|
||||||
@@ -912,9 +942,8 @@ class ShortcutPackage extends ShortcutPackageItem {
|
|||||||
final ComponentName newActivity = newShortcut.getActivity();
|
final ComponentName newActivity = newShortcut.getActivity();
|
||||||
if (newActivity == null) {
|
if (newActivity == null) {
|
||||||
if (operation != ShortcutService.OPERATION_UPDATE) {
|
if (operation != ShortcutService.OPERATION_UPDATE) {
|
||||||
// This method may be called before validating shortcuts, so this may happen,
|
service.wtf("Activity must not be null at this point");
|
||||||
// and is a caller side error.
|
continue; // Just ignore this invalid case.
|
||||||
throw new NullPointerException("Activity must be provided");
|
|
||||||
}
|
}
|
||||||
continue; // Activity can be null for update.
|
continue; // Activity can be null for update.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.content.ComponentName;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.content.res.XmlResourceParser;
|
import android.content.res.XmlResourceParser;
|
||||||
@@ -58,18 +59,36 @@ public class ShortcutParser {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
|
public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
|
||||||
String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException {
|
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;
|
List<ShortcutInfo> result = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (pi != null && pi.activities != null) {
|
final int size = activities.size();
|
||||||
for (ActivityInfo activityInfo : pi.activities) {
|
for (int i = 0; i < size; i++) {
|
||||||
result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
|
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) {
|
} 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(
|
service.wtf(
|
||||||
"Exception caught while parsing shortcut XML for package=" + packageName, e);
|
"Exception caught while parsing shortcut XML for package=" + packageName, e);
|
||||||
return null;
|
return null;
|
||||||
@@ -81,6 +100,11 @@ public class ShortcutParser {
|
|||||||
ShortcutService service,
|
ShortcutService service,
|
||||||
ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
|
ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
|
||||||
List<ShortcutInfo> result) throws IOException, XmlPullParserException {
|
List<ShortcutInfo> result) throws IOException, XmlPullParserException {
|
||||||
|
if (ShortcutService.DEBUG) {
|
||||||
|
Slog.d(TAG, String.format(
|
||||||
|
"Checking main activity %s", activityInfo.getComponentName()));
|
||||||
|
}
|
||||||
|
|
||||||
XmlResourceParser parser = null;
|
XmlResourceParser parser = null;
|
||||||
try {
|
try {
|
||||||
parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
|
parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
|
||||||
@@ -223,7 +247,7 @@ public class ShortcutParser {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, "Unknown tag " + tag + " at depth " + depth);
|
ShortcutService.warnForInvalidTag(depth, tag);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (parser != null) {
|
if (parser != null) {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import android.os.Process;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ResultReceiver;
|
import android.os.ResultReceiver;
|
||||||
import android.os.SELinux;
|
import android.os.SELinux;
|
||||||
|
import android.os.ServiceManager;
|
||||||
import android.os.ShellCommand;
|
import android.os.ShellCommand;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
@@ -76,6 +77,7 @@ import android.util.SparseIntArray;
|
|||||||
import android.util.SparseLongArray;
|
import android.util.SparseLongArray;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.util.Xml;
|
import android.util.Xml;
|
||||||
|
import android.view.IWindowManager;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
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.
|
* - Default launcher check does take a few ms. Worth caching.
|
||||||
*
|
*
|
||||||
* - Detect when already registered instances are passed to APIs again, which might break
|
* - Detect when already registered instances are passed to APIs again, which might break
|
||||||
* internal bitmap handling.
|
* internal bitmap handling.
|
||||||
*
|
*
|
||||||
* - Add more call stats.
|
* - 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 ATTR_VALUE = "value";
|
||||||
|
|
||||||
|
private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
interface ConfigConstants {
|
interface ConfigConstants {
|
||||||
/**
|
/**
|
||||||
@@ -282,7 +286,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
private List<Integer> mDirtyUserIds = new ArrayList<>();
|
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.
|
* 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
|
* 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 =
|
private static final int PACKAGE_MATCH_FLAGS =
|
||||||
PackageManager.MATCH_DIRECT_BOOT_AWARE
|
PackageManager.MATCH_DIRECT_BOOT_AWARE
|
||||||
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
|
||||||
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
|
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@@ -306,13 +311,15 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
int GET_APPLICATION_INFO = 3;
|
int GET_APPLICATION_INFO = 3;
|
||||||
int LAUNCHER_PERMISSION_CHECK = 4;
|
int LAUNCHER_PERMISSION_CHECK = 4;
|
||||||
int CLEANUP_DANGLING_BITMAPS = 5;
|
int CLEANUP_DANGLING_BITMAPS = 5;
|
||||||
int GET_ACTIVITIES_WITH_METADATA = 6;
|
int GET_ACTIVITY_WITH_METADATA = 6;
|
||||||
int GET_INSTALLED_PACKAGES = 7;
|
int GET_INSTALLED_PACKAGES = 7;
|
||||||
int CHECK_PACKAGE_CHANGES = 8;
|
int CHECK_PACKAGE_CHANGES = 8;
|
||||||
int GET_APPLICATION_RESOURCES = 9;
|
int GET_APPLICATION_RESOURCES = 9;
|
||||||
int RESOURCE_NAME_LOOKUP = 10;
|
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();
|
final Object mStatLock = new Object();
|
||||||
@@ -335,9 +342,10 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
OPERATION_SET,
|
OPERATION_SET,
|
||||||
OPERATION_ADD,
|
OPERATION_ADD,
|
||||||
OPERATION_UPDATE
|
OPERATION_UPDATE
|
||||||
})
|
})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@interface ShortcutOperation {}
|
@interface ShortcutOperation {
|
||||||
|
}
|
||||||
|
|
||||||
public ShortcutService(Context context) {
|
public ShortcutService(Context context) {
|
||||||
this(context, BackgroundThread.get().getLooper());
|
this(context, BackgroundThread.get().getLooper());
|
||||||
@@ -373,18 +381,22 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final private IUidObserver mUidObserver = new IUidObserver.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);
|
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);
|
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()
|
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
|
||||||
? (int) parser.getLong(
|
? (int) parser.getLong(
|
||||||
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
|
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
|
||||||
DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
|
DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
|
||||||
: (int) parser.getLong(
|
: (int) parser.getLong(
|
||||||
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
|
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
|
||||||
DEFAULT_MAX_ICON_DIMENSION_DP));
|
DEFAULT_MAX_ICON_DIMENSION_DP));
|
||||||
|
|
||||||
mMaxIconDimension = injectDipToPixel(iconDimensionDp);
|
mMaxIconDimension = injectDipToPixel(iconDimensionDp);
|
||||||
|
|
||||||
@@ -777,7 +789,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
}
|
}
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
// Use the default
|
// Use the default
|
||||||
} catch (IOException|XmlPullParserException e) {
|
} catch (IOException | XmlPullParserException e) {
|
||||||
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
|
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
|
||||||
|
|
||||||
mRawLastResetTime = 0;
|
mRawLastResetTime = 0;
|
||||||
@@ -800,7 +812,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
saveUserInternalLocked(userId, os, /* forBackup= */ false);
|
saveUserInternalLocked(userId, os, /* forBackup= */ false);
|
||||||
|
|
||||||
file.finishWrite(os);
|
file.finishWrite(os);
|
||||||
} catch (XmlPullParserException|IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
|
Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
|
||||||
file.failWrite(os);
|
file.failWrite(os);
|
||||||
}
|
}
|
||||||
@@ -850,10 +862,10 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
|
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
|
||||||
cleanupDanglingBitmapDirectoriesLocked(userId, ret);
|
cleanupDanglingBitmapDirectoriesLocked(userId, ret);
|
||||||
return ret;
|
return ret;
|
||||||
} catch (IOException|XmlPullParserException e) {
|
} catch (IOException | XmlPullParserException e) {
|
||||||
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
|
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1133,7 +1145,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String baseName = String.valueOf(injectCurrentTimeMillis());
|
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 String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
|
||||||
final File file = new File(packagePath, filename);
|
final File file = new File(packagePath, filename);
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
@@ -1205,7 +1217,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
} finally {
|
} finally {
|
||||||
IoUtils.closeQuietly(out);
|
IoUtils.closeQuietly(out);
|
||||||
}
|
}
|
||||||
} catch (IOException|RuntimeException e) {
|
} catch (IOException | RuntimeException e) {
|
||||||
// STOPSHIP Change wtf to e
|
// STOPSHIP Change wtf to e
|
||||||
Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
|
Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
|
||||||
if (path != null && path.exists()) {
|
if (path != null && path.exists()) {
|
||||||
@@ -1284,7 +1296,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
|
|
||||||
private boolean isCallerSystem() {
|
private boolean isCallerSystem() {
|
||||||
final int callingUid = injectBinderCallingUid();
|
final int callingUid = injectBinderCallingUid();
|
||||||
return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
|
return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCallerShell() {
|
private boolean isCallerShell() {
|
||||||
@@ -1349,7 +1361,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalArgumentException if {@code numShortcuts} is bigger than
|
* @throws IllegalArgumentException if {@code numShortcuts} is bigger than
|
||||||
* {@link #getMaxActivityShortcuts()}.
|
* {@link #getMaxActivityShortcuts()}.
|
||||||
*/
|
*/
|
||||||
void enforceMaxActivityShortcuts(int numShortcuts) {
|
void enforceMaxActivityShortcuts(int numShortcuts) {
|
||||||
if (numShortcuts > mMaxShortcuts) {
|
if (numShortcuts > mMaxShortcuts) {
|
||||||
@@ -1402,7 +1414,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
* Clean up / validate an incoming shortcut.
|
* Clean up / validate an incoming shortcut.
|
||||||
* - Make sure all mandatory fields are set.
|
* - Make sure all mandatory fields are set.
|
||||||
* - Make sure the intent's extras are persistable, and them to 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.
|
* - Clear flags.
|
||||||
*
|
*
|
||||||
* TODO Detailed unit tests
|
* TODO Detailed unit tests
|
||||||
@@ -1412,11 +1424,15 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
if (shortcut.getActivity() != null) {
|
if (shortcut.getActivity() != null) {
|
||||||
Preconditions.checkState(
|
Preconditions.checkState(
|
||||||
shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
|
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) {
|
if (!forUpdate) {
|
||||||
shortcut.enforceMandatoryFields();
|
shortcut.enforceMandatoryFields();
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()),
|
||||||
|
"Cannot publish shortcut: " + shortcut.getActivity() + " is not main activity");
|
||||||
}
|
}
|
||||||
if (shortcut.getIcon() != null) {
|
if (shortcut.getIcon() != null) {
|
||||||
ShortcutInfo.validateIcon(shortcut.getIcon());
|
ShortcutInfo.validateIcon(shortcut.getIcon());
|
||||||
@@ -1425,6 +1441,26 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
shortcut.replaceFlags(0);
|
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) {
|
private void assignImplicitRanks(List<ShortcutInfo> shortcuts) {
|
||||||
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
for (int i = shortcuts.size() - 1; i >= 0; i--) {
|
||||||
shortcuts.get(i).setImplicitRank(i);
|
shortcuts.get(i).setImplicitRank(i);
|
||||||
@@ -1446,6 +1482,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
|
|
||||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||||
|
|
||||||
|
fillInDefaultActivity(newShortcuts);
|
||||||
|
|
||||||
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
|
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
|
||||||
|
|
||||||
// Throttling.
|
// Throttling.
|
||||||
@@ -1493,6 +1531,9 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
|
|
||||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
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);
|
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
|
||||||
|
|
||||||
// Throttling.
|
// Throttling.
|
||||||
@@ -1573,6 +1614,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
|
|
||||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||||
|
|
||||||
|
fillInDefaultActivity(newShortcuts);
|
||||||
|
|
||||||
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
|
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
|
||||||
|
|
||||||
// Initialize the implicit ranks for ShortcutPackage.adjustRanks().
|
// 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
|
@Override
|
||||||
public void resetThrottling() {
|
public void resetThrottling() {
|
||||||
@@ -1917,10 +1961,12 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
|
|
||||||
// === House keeping ===
|
// === House keeping ===
|
||||||
|
|
||||||
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId) {
|
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
|
||||||
|
boolean appStillExists) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
forEachLoadedUserLocked(user ->
|
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.
|
* This is called when an app is uninstalled, or an app gets "clear data"ed.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@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 boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
|
||||||
|
|
||||||
final ShortcutUser user = getUserShortcutsLocked(owningUserId);
|
final ShortcutUser user = getUserShortcutsLocked(owningUserId);
|
||||||
@@ -1961,6 +2008,13 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
notifyListeners(packageName, owningUserId);
|
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) {
|
if (!wasUserLoaded) {
|
||||||
// Note this will execute the scheduled save.
|
// Note this will execute the scheduled save.
|
||||||
unloadUserLocked(owningUserId);
|
unloadUserLocked(owningUserId);
|
||||||
@@ -2269,19 +2323,29 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
public void onPackageDataCleared(String packageName, int uid) {
|
public void onPackageDataCleared(String packageName, int uid) {
|
||||||
handlePackageDataCleared(packageName, getChangingUserId());
|
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.
|
* Called when a user is unlocked.
|
||||||
* - Check all known packages still exist, and otherwise perform cleanup.
|
* - 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
|
* - 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
|
@VisibleForTesting
|
||||||
void checkPackageChanges(@UserIdInt int ownerUserId) {
|
void checkPackageChanges(@UserIdInt int ownerUserId) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
|
Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
|
||||||
}
|
}
|
||||||
|
if (injectIsSafeModeEnabled()) {
|
||||||
|
Slog.i(TAG, "Safe mode, skipping checkPackageChanges()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final long start = injectElapsedRealtime();
|
final long start = injectElapsedRealtime();
|
||||||
try {
|
try {
|
||||||
@@ -2302,14 +2366,15 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
if (gonePackages.size() > 0) {
|
if (gonePackages.size() > 0) {
|
||||||
for (int i = gonePackages.size() - 1; i >= 0; i--) {
|
for (int i = gonePackages.size() - 1; i >= 0; i--) {
|
||||||
final PackageWithUser pu = gonePackages.get(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();
|
final long now = injectCurrentTimeMillis();
|
||||||
|
|
||||||
// Then for each installed app, publish manifest shortcuts when needed.
|
// Then for each installed app, publish manifest shortcuts when needed.
|
||||||
forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
|
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
|
// 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) {
|
synchronized (mLock) {
|
||||||
final ShortcutUser user = getUserShortcutsLocked(userId);
|
final ShortcutUser user = getUserShortcutsLocked(userId);
|
||||||
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
|
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
|
||||||
user.handlePackageAddedOrUpdated(packageName);
|
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
|
||||||
}
|
}
|
||||||
verifyStates();
|
verifyStates();
|
||||||
}
|
}
|
||||||
@@ -2345,7 +2410,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
|
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
|
||||||
|
|
||||||
if (isPackageInstalled(packageName, userId)) {
|
if (isPackageInstalled(packageName, userId)) {
|
||||||
user.handlePackageAddedOrUpdated(packageName);
|
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verifyStates();
|
verifyStates();
|
||||||
@@ -2356,7 +2421,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
|
Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
|
||||||
packageUserId));
|
packageUserId));
|
||||||
}
|
}
|
||||||
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
|
cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false);
|
||||||
|
|
||||||
verifyStates();
|
verifyStates();
|
||||||
}
|
}
|
||||||
@@ -2366,7 +2431,23 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
|
Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
|
||||||
packageUserId));
|
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();
|
verifyStates();
|
||||||
}
|
}
|
||||||
@@ -2405,7 +2486,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
final long token = injectClearCallingIdentity();
|
final long token = injectClearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
|
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
|
||||||
| (getSignatures ? PackageManager.GET_SIGNATURES : 0)
|
| (getSignatures ? PackageManager.GET_SIGNATURES : 0)
|
||||||
, userId);
|
, userId);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// Shouldn't happen.
|
// Shouldn't happen.
|
||||||
@@ -2439,14 +2520,12 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@VisibleForTesting
|
ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) {
|
||||||
PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
|
|
||||||
final long start = injectElapsedRealtime();
|
final long start = injectElapsedRealtime();
|
||||||
final long token = injectClearCallingIdentity();
|
final long token = injectClearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
return mIPackageManager.getPackageInfo(packageName,
|
return mIPackageManager.getActivityInfo(activity,
|
||||||
PACKAGE_MATCH_FLAGS | PackageManager.GET_ACTIVITIES
|
PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId);
|
||||||
| PackageManager.GET_META_DATA, userId);
|
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// Shouldn't happen.
|
// Shouldn't happen.
|
||||||
Slog.wtf(TAG, "RemoteException", e);
|
Slog.wtf(TAG, "RemoteException", e);
|
||||||
@@ -2454,7 +2533,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
} finally {
|
} finally {
|
||||||
injectRestoreCallingIdentity(token);
|
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 ===
|
// === Backup & restore ===
|
||||||
|
|
||||||
boolean shouldBackupApp(String packageName, int userId) {
|
boolean shouldBackupApp(String packageName, int userId) {
|
||||||
@@ -2560,7 +2719,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
|
final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
|
||||||
try {
|
try {
|
||||||
saveUserInternalLocked(userId, os, /* forBackup */ true);
|
saveUserInternalLocked(userId, os, /* forBackup */ true);
|
||||||
} catch (XmlPullParserException|IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
// Shouldn't happen.
|
// Shouldn't happen.
|
||||||
Slog.w(TAG, "Backup failed.", e);
|
Slog.w(TAG, "Backup failed.", e);
|
||||||
return null;
|
return null;
|
||||||
@@ -2579,7 +2738,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
final ByteArrayInputStream is = new ByteArrayInputStream(payload);
|
final ByteArrayInputStream is = new ByteArrayInputStream(payload);
|
||||||
try {
|
try {
|
||||||
user = loadUserInternal(userId, is, /* fromBackup */ true);
|
user = loadUserInternal(userId, is, /* fromBackup */ true);
|
||||||
} catch (XmlPullParserException|IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
Slog.w(TAG, "Restoration failed.", e);
|
Slog.w(TAG, "Restoration failed.", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2656,7 +2815,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
pw.println(mResetInterval);
|
pw.println(mResetInterval);
|
||||||
pw.print(" maxUpdatesPerInterval: ");
|
pw.print(" maxUpdatesPerInterval: ");
|
||||||
pw.println(mMaxUpdatesPerInterval);
|
pw.println(mMaxUpdatesPerInterval);
|
||||||
pw.print(" maxDynamicShortcuts: ");
|
pw.print(" maxShortcutsPerActivity: ");
|
||||||
pw.println(mMaxShortcuts);
|
pw.println(mMaxShortcuts);
|
||||||
pw.println();
|
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_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
|
||||||
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
|
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
|
||||||
dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
|
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.GET_INSTALLED_PACKAGES, "getInstalledPackages");
|
||||||
dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
|
dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
|
||||||
dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources");
|
dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources");
|
||||||
dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup");
|
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++) {
|
for (int i = 0; i < mUsers.size(); i++) {
|
||||||
@@ -2725,7 +2886,9 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
|
|
||||||
enforceShell();
|
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 {
|
static class CommandException extends Exception {
|
||||||
@@ -2796,6 +2959,9 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
case "clear-shortcuts":
|
case "clear-shortcuts":
|
||||||
handleClearShortcuts();
|
handleClearShortcuts();
|
||||||
break;
|
break;
|
||||||
|
case "verify-states": // hidden command to verify various internal states.
|
||||||
|
handleVerifyStates();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return handleDefaultCommands(cmd);
|
return handleDefaultCommands(cmd);
|
||||||
}
|
}
|
||||||
@@ -2938,7 +3104,16 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
|
|
||||||
Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName);
|
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) {
|
final void wtf(String message) {
|
||||||
wtf( message, /* exception= */ null);
|
wtf(message, /* exception= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Injection point.
|
// Injection point.
|
||||||
@@ -3092,6 +3267,10 @@ public class ShortcutService extends IShortcutService.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final void verifyStatesForce() {
|
||||||
|
verifyStatesInner();
|
||||||
|
}
|
||||||
|
|
||||||
private void verifyStatesInner() {
|
private void verifyStatesInner() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
|
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class ShortcutUser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
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 ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
|
||||||
|
|
||||||
private final SparseArray<ShortcutPackage> mPackagesFromUid = new SparseArray<>();
|
|
||||||
|
|
||||||
private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
|
private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
|
||||||
|
|
||||||
/** Default launcher that can access the launcher apps APIs. */
|
/** 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 boolean isNewApp = !mPackages.containsKey(packageName);
|
||||||
|
|
||||||
final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
|
final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
|
||||||
|
|
||||||
if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) {
|
if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp, forceRescan)) {
|
||||||
if (isNewApp) {
|
if (isNewApp) {
|
||||||
mPackages.remove(packageName);
|
mPackages.remove(packageName);
|
||||||
}
|
}
|
||||||
@@ -381,8 +379,10 @@ class ShortcutUser {
|
|||||||
pw.print(mUserId);
|
pw.print(mUserId);
|
||||||
pw.print(" Known locale seq#: ");
|
pw.print(" Known locale seq#: ");
|
||||||
pw.print(mKnownLocaleChangeSequenceNumber);
|
pw.print(mKnownLocaleChangeSequenceNumber);
|
||||||
pw.print(" Last app scan: ");
|
pw.print(" Last app scan: [");
|
||||||
pw.print(mLastAppScanTime);
|
pw.print(mLastAppScanTime);
|
||||||
|
pw.print("] ");
|
||||||
|
pw.print(ShortcutService.formatTime(mLastAppScanTime));
|
||||||
pw.println();
|
pw.println();
|
||||||
|
|
||||||
prefix += prefix + " ";
|
prefix += prefix + " ";
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import android.content.pm.LauncherApps.ShortcutQuery;
|
|||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.PackageManagerInternal;
|
import android.content.pm.PackageManagerInternal;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.content.pm.ShortcutManager;
|
import android.content.pm.ShortcutManager;
|
||||||
import android.content.pm.ShortcutServiceInternal;
|
import android.content.pm.ShortcutServiceInternal;
|
||||||
@@ -98,8 +99,11 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
||||||
protected static final String TAG = "ShortcutManagerTest";
|
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[] EMPTY_STRINGS = new String[0]; // Just for readability.
|
||||||
|
|
||||||
|
protected static final String MAIN_ACTIVITY_CLASS = "MainActivity";
|
||||||
|
|
||||||
// public for mockito
|
// public for mockito
|
||||||
public class BaseContext extends MockContext {
|
public class BaseContext extends MockContext {
|
||||||
@Override
|
@Override
|
||||||
@@ -311,8 +317,55 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
|
ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity,
|
||||||
return mContext.injectGetActivitiesWithMetadata(packageName, userId);
|
@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
|
@Override
|
||||||
@@ -334,6 +387,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean injectIsSafeModeEnabled() {
|
||||||
|
return mSafeMode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void wtf(String message, Exception e) {
|
void wtf(String message, Exception e) {
|
||||||
// During tests, WTF is fatal.
|
// During tests, WTF is fatal.
|
||||||
@@ -441,6 +499,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
protected File mInjectedFilePathRoot;
|
protected File mInjectedFilePathRoot;
|
||||||
|
|
||||||
|
protected boolean mSafeMode;
|
||||||
|
|
||||||
protected long mInjectedCurrentTimeMillis;
|
protected long mInjectedCurrentTimeMillis;
|
||||||
|
|
||||||
protected boolean mInjectedIsLowRamDevice;
|
protected boolean mInjectedIsLowRamDevice;
|
||||||
@@ -512,6 +572,15 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
|||||||
LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
|
LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
|
||||||
|| LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.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 START_TIME = 1440000000101L;
|
||||||
|
|
||||||
protected static final long INTERVAL = 10000;
|
protected static final long INTERVAL = 10000;
|
||||||
@@ -1051,10 +1120,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
|||||||
+ "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
|
+ "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void waitOnMainThread() throws Throwable {
|
|
||||||
runTestOnUiThread(() -> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a shortcut with an ID.
|
* Make a shortcut with an ID.
|
||||||
*/
|
*/
|
||||||
@@ -1410,6 +1475,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
|||||||
return i;
|
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) {
|
protected Intent genPackageDataClear(String packageName, int userId) {
|
||||||
Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
|
Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
|
||||||
i.setData(Uri.parse("package:" + packageName));
|
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.assertDynamicShortcutCountExceeded;
|
||||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty;
|
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.assertForLauncherCallback;
|
||||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
|
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.assertWith;
|
||||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.filterByActivity;
|
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.pfdToBitmap;
|
||||||
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAll;
|
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.set;
|
||||||
|
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.waitOnMainThread;
|
||||||
|
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Matchers.anyInt;
|
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.ConfigConstants;
|
||||||
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
|
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
|
||||||
import com.android.server.pm.ShortcutUser.PackageWithUser;
|
import com.android.server.pm.ShortcutUser.PackageWithUser;
|
||||||
|
import com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.ShortcutListAsserter;
|
||||||
|
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
@@ -91,6 +94,9 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for ShortcutService and ShortcutManager.
|
* 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() {
|
public void testDeleteDynamicShortcuts() {
|
||||||
final ShortcutInfo si1 = makeShortcut("shortcut1");
|
final ShortcutInfo si1 = makeShortcut("shortcut1");
|
||||||
final ShortcutInfo si2 = makeShortcut("shortcut2");
|
final ShortcutInfo si2 = makeShortcut("shortcut2");
|
||||||
@@ -2336,151 +2463,87 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
+ ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
|
+ ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
|
||||||
);
|
);
|
||||||
|
|
||||||
LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
|
setCaller(LAUNCHER_1, USER_0);
|
||||||
|
|
||||||
// Set listeners
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
|
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||||
runWithCaller(LAUNCHER_1, USER_0, () -> {
|
assertTrue(mManager.setDynamicShortcuts(list(
|
||||||
mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper()));
|
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||||
});
|
});
|
||||||
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||||
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())
|
|
||||||
.haveIds("s1", "s2", "s3")
|
.haveIds("s1", "s2", "s3")
|
||||||
.areAllWithKeyFieldsOnly()
|
.areAllWithKeyFieldsOnly()
|
||||||
.areAllDynamic();
|
.areAllDynamic();
|
||||||
|
|
||||||
// From different package.
|
// From different package.
|
||||||
reset(c0);
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
|
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
|
||||||
assertTrue(mManager.setDynamicShortcuts(list(
|
assertTrue(mManager.setDynamicShortcuts(list(
|
||||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||||
});
|
});
|
||||||
waitOnMainThread();
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_2, HANDLE_USER_0)
|
||||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
|
||||||
verify(c0).onShortcutsChanged(
|
|
||||||
eq(CALLING_PACKAGE_2),
|
|
||||||
shortcuts.capture(),
|
|
||||||
eq(HANDLE_USER_0)
|
|
||||||
);
|
|
||||||
assertWith(shortcuts.getValue())
|
|
||||||
.haveIds("s1", "s2", "s3")
|
.haveIds("s1", "s2", "s3")
|
||||||
.areAllWithKeyFieldsOnly()
|
.areAllWithKeyFieldsOnly()
|
||||||
.areAllDynamic();
|
.areAllDynamic();
|
||||||
|
|
||||||
// Different user, callback shouldn't be called.
|
// Different user, callback shouldn't be called.
|
||||||
reset(c0);
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
|
||||||
assertTrue(mManager.setDynamicShortcuts(list(
|
assertTrue(mManager.setDynamicShortcuts(list(
|
||||||
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
|
||||||
});
|
});
|
||||||
waitOnMainThread();
|
}).assertNoCallbackCalled();
|
||||||
verify(c0, times(0)).onShortcutsChanged(
|
|
||||||
anyString(),
|
|
||||||
any(List.class),
|
|
||||||
any(UserHandle.class)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test for addDynamicShortcuts.
|
// Test for addDynamicShortcuts.
|
||||||
reset(c0);
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||||
dumpsysOnLogcat("before addDynamicShortcuts");
|
assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
|
||||||
assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
|
});
|
||||||
});
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||||
|
|
||||||
waitOnMainThread();
|
|
||||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
|
||||||
verify(c0).onShortcutsChanged(
|
|
||||||
eq(CALLING_PACKAGE_1),
|
|
||||||
shortcuts.capture(),
|
|
||||||
eq(HANDLE_USER_0)
|
|
||||||
);
|
|
||||||
assertWith(shortcuts.getValue())
|
|
||||||
.haveIds("s1", "s2", "s3", "s4")
|
.haveIds("s1", "s2", "s3", "s4")
|
||||||
.areAllWithKeyFieldsOnly()
|
.areAllWithKeyFieldsOnly()
|
||||||
.areAllDynamic();
|
.areAllDynamic();
|
||||||
|
|
||||||
// Test for remove
|
// Test for remove
|
||||||
reset(c0);
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||||
mManager.removeDynamicShortcuts(list("s1"));
|
mManager.removeDynamicShortcuts(list("s1"));
|
||||||
});
|
});
|
||||||
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||||
waitOnMainThread();
|
|
||||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
|
||||||
verify(c0).onShortcutsChanged(
|
|
||||||
eq(CALLING_PACKAGE_1),
|
|
||||||
shortcuts.capture(),
|
|
||||||
eq(HANDLE_USER_0)
|
|
||||||
);
|
|
||||||
assertWith(shortcuts.getValue())
|
|
||||||
.haveIds("s2", "s3", "s4")
|
.haveIds("s2", "s3", "s4")
|
||||||
.areAllWithKeyFieldsOnly()
|
.areAllWithKeyFieldsOnly()
|
||||||
.areAllDynamic();
|
.areAllDynamic();
|
||||||
|
|
||||||
// Test for update
|
// Test for update
|
||||||
reset(c0);
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||||
assertTrue(mManager.updateShortcuts(list(
|
assertTrue(mManager.updateShortcuts(list(
|
||||||
makeShortcut("s1"), makeShortcut("s2"))));
|
makeShortcut("s1"), makeShortcut("s2"))));
|
||||||
});
|
});
|
||||||
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||||
waitOnMainThread();
|
// All remaining shortcuts will be passed regardless of what's been updated.
|
||||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
|
||||||
verify(c0).onShortcutsChanged(
|
|
||||||
eq(CALLING_PACKAGE_1),
|
|
||||||
shortcuts.capture(),
|
|
||||||
eq(HANDLE_USER_0)
|
|
||||||
);
|
|
||||||
assertWith(shortcuts.getValue())
|
|
||||||
.haveIds("s2", "s3", "s4")
|
.haveIds("s2", "s3", "s4")
|
||||||
.areAllWithKeyFieldsOnly()
|
.areAllWithKeyFieldsOnly()
|
||||||
.areAllDynamic();
|
.areAllDynamic();
|
||||||
|
|
||||||
// Test for deleteAll
|
// Test for deleteAll
|
||||||
reset(c0);
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||||
mManager.removeAllDynamicShortcuts();
|
mManager.removeAllDynamicShortcuts();
|
||||||
});
|
});
|
||||||
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||||
waitOnMainThread();
|
|
||||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
|
||||||
verify(c0).onShortcutsChanged(
|
|
||||||
eq(CALLING_PACKAGE_1),
|
|
||||||
shortcuts.capture(),
|
|
||||||
eq(HANDLE_USER_0)
|
|
||||||
);
|
|
||||||
assertWith(shortcuts.getValue())
|
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
|
|
||||||
// Update package1 with manifest shortcuts
|
// Update package1 with manifest shortcuts
|
||||||
reset(c0);
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
addManifestShortcutResource(
|
addManifestShortcutResource(
|
||||||
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
|
||||||
R.xml.shortcut_2);
|
R.xml.shortcut_2);
|
||||||
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
updatePackageVersion(CALLING_PACKAGE_1, 1);
|
||||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||||
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||||
waitOnMainThread();
|
|
||||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
|
||||||
verify(c0).onShortcutsChanged(
|
|
||||||
eq(CALLING_PACKAGE_1),
|
|
||||||
shortcuts.capture(),
|
|
||||||
eq(HANDLE_USER_0)
|
|
||||||
);
|
|
||||||
assertWith(shortcuts.getValue())
|
|
||||||
.areAllManifest()
|
.areAllManifest()
|
||||||
.areAllWithKeyFieldsOnly()
|
.areAllWithKeyFieldsOnly()
|
||||||
.haveIds("ms1", "ms2");
|
.haveIds("ms1", "ms2");
|
||||||
@@ -2518,58 +2581,42 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
mService.mPackageMonitor.onReceive(getTestContext(),
|
mService.mPackageMonitor.onReceive(getTestContext(),
|
||||||
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
|
||||||
|
|
||||||
reset(c0); // Check the callback for the next API call.
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
|
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||||
mManager.removeDynamicShortcuts(list("s2"));
|
mManager.removeDynamicShortcuts(list("s2"));
|
||||||
|
|
||||||
assertWith(getCallerShortcuts())
|
assertWith(getCallerShortcuts())
|
||||||
.haveIds("ms2", "s1", "s2")
|
.haveIds("ms2", "s1", "s2")
|
||||||
|
|
||||||
.selectByIds("ms2")
|
.selectByIds("ms2")
|
||||||
.areAllNotManifest()
|
.areAllNotManifest()
|
||||||
.areAllPinned()
|
.areAllPinned()
|
||||||
.areAllImmutable()
|
.areAllImmutable()
|
||||||
.areAllDisabled()
|
.areAllDisabled()
|
||||||
|
|
||||||
.revertToOriginalList()
|
.revertToOriginalList()
|
||||||
.selectByIds("s1")
|
.selectByIds("s1")
|
||||||
.areAllDynamic()
|
.areAllDynamic()
|
||||||
.areAllNotPinned()
|
.areAllNotPinned()
|
||||||
.areAllEnabled()
|
.areAllEnabled()
|
||||||
|
|
||||||
.revertToOriginalList()
|
.revertToOriginalList()
|
||||||
.selectByIds("s2")
|
.selectByIds("s2")
|
||||||
.areAllNotDynamic()
|
.areAllNotDynamic()
|
||||||
.areAllPinned()
|
.areAllPinned()
|
||||||
.areAllEnabled()
|
.areAllEnabled()
|
||||||
;
|
;
|
||||||
});
|
});
|
||||||
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
|
||||||
waitOnMainThread();
|
|
||||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
|
||||||
verify(c0).onShortcutsChanged(
|
|
||||||
eq(CALLING_PACKAGE_1),
|
|
||||||
shortcuts.capture(),
|
|
||||||
eq(HANDLE_USER_0)
|
|
||||||
);
|
|
||||||
assertWith(shortcuts.getValue())
|
|
||||||
.haveIds("ms2", "s1", "s2")
|
.haveIds("ms2", "s1", "s2")
|
||||||
.areAllWithKeyFieldsOnly();
|
.areAllWithKeyFieldsOnly();
|
||||||
|
|
||||||
// Remove CALLING_PACKAGE_2
|
// Remove CALLING_PACKAGE_2
|
||||||
reset(c0);
|
assertForLauncherCallback(mLauncherApps, () -> {
|
||||||
uninstallPackage(USER_0, CALLING_PACKAGE_2);
|
uninstallPackage(USER_0, CALLING_PACKAGE_2);
|
||||||
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
|
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0,
|
||||||
|
/* appStillExists = */ false);
|
||||||
// Should get a callback with an empty list.
|
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_2, HANDLE_USER_0)
|
||||||
waitOnMainThread();
|
|
||||||
shortcuts = ArgumentCaptor.forClass(List.class);
|
|
||||||
verify(c0).onShortcutsChanged(
|
|
||||||
eq(CALLING_PACKAGE_2),
|
|
||||||
shortcuts.capture(),
|
|
||||||
eq(HANDLE_USER_0)
|
|
||||||
);
|
|
||||||
assertWith(shortcuts.getValue())
|
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2970,7 +3017,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
|
|
||||||
// Nonexistent package.
|
// Nonexistent package.
|
||||||
uninstallPackage(USER_0, "abc");
|
uninstallPackage(USER_0, "abc");
|
||||||
mService.cleanUpPackageLocked("abc", USER_0, USER_0);
|
mService.cleanUpPackageLocked("abc", USER_0, USER_0, /* appStillExists = */ false);
|
||||||
|
|
||||||
// No changes.
|
// No changes.
|
||||||
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
|
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
|
||||||
@@ -3002,7 +3049,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
|
|
||||||
// Remove a package.
|
// Remove a package.
|
||||||
uninstallPackage(USER_0, CALLING_PACKAGE_1);
|
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),
|
assertEquals(set(CALLING_PACKAGE_2),
|
||||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||||
@@ -3033,7 +3081,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
|
|
||||||
// Remove a launcher.
|
// Remove a launcher.
|
||||||
uninstallPackage(USER_10, LAUNCHER_1);
|
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),
|
assertEquals(set(CALLING_PACKAGE_2),
|
||||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||||
@@ -3061,7 +3109,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
|
|
||||||
// Remove a package.
|
// Remove a package.
|
||||||
uninstallPackage(USER_10, CALLING_PACKAGE_2);
|
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),
|
assertEquals(set(CALLING_PACKAGE_2),
|
||||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||||
@@ -3089,7 +3138,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
|
|
||||||
// Remove the other launcher from user 10 too.
|
// Remove the other launcher from user 10 too.
|
||||||
uninstallPackage(USER_10, LAUNCHER_2);
|
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),
|
assertEquals(set(CALLING_PACKAGE_2),
|
||||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||||
@@ -3117,7 +3167,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
|
|
||||||
// More remove.
|
// More remove.
|
||||||
uninstallPackage(USER_10, CALLING_PACKAGE_1);
|
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),
|
assertEquals(set(CALLING_PACKAGE_2),
|
||||||
hashSet(user0.getAllPackagesForTest().keySet()));
|
hashSet(user0.getAllPackagesForTest().keySet()));
|
||||||
@@ -3143,6 +3194,74 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
mService.saveDirtyInfo();
|
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() {
|
public void testHandleGonePackage_crossProfile() {
|
||||||
// Create some shortcuts.
|
// Create some shortcuts.
|
||||||
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
|
||||||
@@ -3454,6 +3573,20 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
assertTrue(mManager.addDynamicShortcuts(list(
|
assertTrue(mManager.addDynamicShortcuts(list(
|
||||||
makeShortcutWithIcon("s1", bmp32x32), makeShortcutWithIcon("s2", bmp32x32)
|
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);
|
setCaller(CALLING_PACKAGE_2, USER_0);
|
||||||
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
|
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
|
||||||
@@ -3629,8 +3762,47 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
|
|||||||
assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
|
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.
|
// Set up shortcuts and launchers.
|
||||||
|
|
||||||
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
|
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() {
|
protected void prepareForBackupTest() {
|
||||||
|
|
||||||
prepareCrossProfileDataSet();
|
prepareCrossProfileDataSet();
|
||||||
|
|||||||
@@ -72,18 +72,68 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
|
|||||||
"ID must be provided",
|
"ID must be provided",
|
||||||
() -> new ShortcutInfo.Builder(getTestContext()).build());
|
() -> new ShortcutInfo.Builder(getTestContext()).build());
|
||||||
|
|
||||||
assertExpectException(NullPointerException.class, "Intent action must be set",
|
assertExpectException(
|
||||||
() -> new ShortcutInfo.Builder(getTestContext()).setIntent(new Intent()));
|
RuntimeException.class,
|
||||||
|
"id cannot be empty",
|
||||||
assertExpectException(NullPointerException.class, "Activity must be provided", () -> {
|
() -> new ShortcutInfo.Builder(getTestContext(), null));
|
||||||
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(), ""));
|
||||||
|
|
||||||
|
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(
|
assertExpectException(
|
||||||
IllegalArgumentException.class, "Short label must be provided", () -> {
|
IllegalArgumentException.class, "Short label must be provided", () -> {
|
||||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||||
.setId("id")
|
|
||||||
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
||||||
.build();
|
.build();
|
||||||
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
||||||
@@ -91,25 +141,24 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
|
|||||||
|
|
||||||
assertExpectException(
|
assertExpectException(
|
||||||
IllegalArgumentException.class, "Short label must be provided", () -> {
|
IllegalArgumentException.class, "Short label must be provided", () -> {
|
||||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||||
.setId("id")
|
|
||||||
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
||||||
.build();
|
.build();
|
||||||
assertTrue(getManager().addDynamicShortcuts(list(si)));
|
assertTrue(getManager().addDynamicShortcuts(list(si)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// same for add.
|
||||||
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
|
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
|
||||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||||
.setId("id")
|
|
||||||
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
||||||
.setShortLabel("x")
|
.setShortLabel("x")
|
||||||
.build();
|
.build();
|
||||||
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// same for add.
|
||||||
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
|
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
|
||||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||||
.setId("id")
|
|
||||||
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
|
||||||
.setShortLabel("x")
|
.setShortLabel("x")
|
||||||
.build();
|
.build();
|
||||||
@@ -117,18 +166,17 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertExpectException(
|
assertExpectException(
|
||||||
IllegalStateException.class, "package name mismatch", () -> {
|
IllegalStateException.class, "does not belong to package", () -> {
|
||||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||||
.setId("id")
|
|
||||||
.setActivity(new ComponentName("xxx", "s"))
|
.setActivity(new ComponentName("xxx", "s"))
|
||||||
.build();
|
.build();
|
||||||
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
assertTrue(getManager().setDynamicShortcuts(list(si)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// same for add.
|
||||||
assertExpectException(
|
assertExpectException(
|
||||||
IllegalStateException.class, "package name mismatch", () -> {
|
IllegalStateException.class, "does not belong to package", () -> {
|
||||||
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
|
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
|
||||||
.setId("id")
|
|
||||||
.setActivity(new ComponentName("xxx", "s"))
|
.setActivity(new ComponentName("xxx", "s"))
|
||||||
.build();
|
.build();
|
||||||
assertTrue(getManager().addDynamicShortcuts(list(si)));
|
assertTrue(getManager().addDynamicShortcuts(list(si)));
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ import static junit.framework.Assert.assertTrue;
|
|||||||
import static junit.framework.Assert.fail;
|
import static junit.framework.Assert.fail;
|
||||||
|
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.anyInt;
|
||||||
import static org.mockito.Matchers.anyList;
|
import static org.mockito.Matchers.anyList;
|
||||||
import static org.mockito.Matchers.anyString;
|
import static org.mockito.Matchers.anyString;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -35,11 +37,14 @@ import android.app.Instrumentation;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.LauncherApps;
|
import android.content.pm.LauncherApps;
|
||||||
|
import android.content.pm.LauncherApps.Callback;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.os.BaseBundle;
|
import android.os.BaseBundle;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.PersistableBundle;
|
import android.os.PersistableBundle;
|
||||||
@@ -54,6 +59,7 @@ import junit.framework.AssertionFailedError;
|
|||||||
import org.hamcrest.BaseMatcher;
|
import org.hamcrest.BaseMatcher;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
@@ -69,6 +75,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@@ -664,8 +671,8 @@ public class ShortcutManagerTestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ShortcutListAsserter(ShortcutListAsserter original, List<ShortcutInfo> list) {
|
private ShortcutListAsserter(ShortcutListAsserter original, List<ShortcutInfo> list) {
|
||||||
mOriginal = original == null ? this : original;
|
mOriginal = (original == null) ? this : original;
|
||||||
mList = new ArrayList<>(list);
|
mList = (list == null) ? new ArrayList<>(0) : new ArrayList<>(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShortcutListAsserter revertToOriginalList() {
|
public ShortcutListAsserter revertToOriginalList() {
|
||||||
@@ -813,6 +820,11 @@ public class ShortcutManagerTestUtils {
|
|||||||
return this;
|
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) {
|
public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
|
||||||
boolean found = false;
|
boolean found = false;
|
||||||
for (int i = 0; i < mList.size(); i++) {
|
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