Merge \"Shortcut: Only \"main\" activities can have shortcuts.\" into nyc-mr1-dev

am: 1af9a58956

Change-Id: Id878608aec49985228e129de9736b751c9ae8567
This commit is contained in:
Makoto Onuki
2016-06-24 21:42:54 +00:00
committed by android-build-merger
9 changed files with 1082 additions and 276 deletions

View File

@@ -709,7 +709,7 @@ public final class ShortcutInfo implements Parcelable {
@NonNull
@Deprecated
public Builder setId(@NonNull String id) {
mId = Preconditions.checkStringNotEmpty(id, "id");
mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty");
return this;
}
@@ -721,14 +721,17 @@ public final class ShortcutInfo implements Parcelable {
*/
public Builder(Context context, String id) {
mContext = context;
mId = Preconditions.checkStringNotEmpty(id, "id");
mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty");
}
/**
* Sets the target activity. A shortcut will be shown with this activity on the launcher.
*
* <p>This is a mandatory field, unless it's passed to
* {@link ShortcutManager#updateShortcuts(List)}.
* <p>Only "main" activities -- i.e. ones with an intent filter for
* {@link Intent#ACTION_MAIN} and {@link Intent#CATEGORY_LAUNCHER} can be target activities.
*
* <p>By default, the first main activity defined in the application manifest will be
* the target.
*
* <p>The package name of the target activity must match the package name of the shortcut
* publisher.
@@ -738,7 +741,7 @@ public final class ShortcutInfo implements Parcelable {
*/
@NonNull
public Builder setActivity(@NonNull ComponentName activity) {
mActivity = Preconditions.checkNotNull(activity, "activity");
mActivity = Preconditions.checkNotNull(activity, "activity cannot be null");
return this;
}
@@ -785,7 +788,7 @@ public final class ShortcutInfo implements Parcelable {
@NonNull
public Builder setShortLabel(@NonNull CharSequence shortLabel) {
Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set");
mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel");
mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty");
return this;
}
@@ -810,7 +813,7 @@ public final class ShortcutInfo implements Parcelable {
@NonNull
public Builder setLongLabel(@NonNull CharSequence longLabel) {
Preconditions.checkState(mTextResId == 0, "longLabelResId already set");
mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel");
mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty");
return this;
}
@@ -854,7 +857,8 @@ public final class ShortcutInfo implements Parcelable {
Preconditions.checkState(
mDisabledMessageResId == 0, "disabledMessageResId already set");
mDisabledMessage =
Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage");
Preconditions.checkStringNotEmpty(disabledMessage,
"disabledMessage cannot be empty");
return this;
}
@@ -876,8 +880,8 @@ public final class ShortcutInfo implements Parcelable {
*/
@NonNull
public Builder setIntent(@NonNull Intent intent) {
mIntent = Preconditions.checkNotNull(intent, "intent");
Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set");
mIntent = Preconditions.checkNotNull(intent, "intent cannot be null");
Preconditions.checkNotNull(mIntent.getAction(), "intent's action must be set");
return this;
}
@@ -944,6 +948,11 @@ public final class ShortcutInfo implements Parcelable {
return mActivity;
}
/** @hide */
public void setActivity(ComponentName activity) {
mActivity = activity;
}
/**
* Icon.
*

View File

@@ -54,8 +54,6 @@ import java.util.function.Predicate;
* User information used by {@link ShortcutService}.
*
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
*
* TODO Max dynamic shortcuts cap should be per activity.
*/
class ShortcutPackage extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
@@ -321,9 +319,27 @@ class ShortcutPackage extends ShortcutPackageItem {
/**
* Remove a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
* is pinned, it'll remain as a pinned shortcut, and is still enabled.
*
* @return true if it's actually removed because it wasn't pinned, or false if it's still
* pinned.
*/
public void deleteDynamicWithId(@NonNull String shortcutId) {
deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
public boolean deleteDynamicWithId(@NonNull String shortcutId) {
final ShortcutInfo removed = deleteOrDisableWithId(
shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
return removed == null;
}
/**
* Disable a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut
* is pinned, it'll remain as a pinned shortcut, but will be disabled.
*
* @return true if it's actually removed because it wasn't pinned, or false if it's still
* pinned.
*/
private boolean disableDynamicWithId(@NonNull String shortcutId) {
final ShortcutInfo disabled = deleteOrDisableWithId(
shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false);
return disabled == null;
}
/**
@@ -599,14 +615,14 @@ class ShortcutPackage extends ShortcutPackageItem {
*
* @return TRUE if any shortcuts have been changed.
*/
public boolean handlePackageAddedOrUpdated(boolean isNewApp) {
public boolean handlePackageAddedOrUpdated(boolean isNewApp, boolean forceRescan) {
final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
getPackageName(), getPackageUserId());
if (pi == null) {
return false; // Shouldn't happen.
}
if (!isNewApp) {
if (!isNewApp && !forceRescan) {
// Make sure the version code or last update time has changed.
// Otherwise, nothing to do.
if (getPackageInfo().getVersionCode() >= pi.versionCode
@@ -649,12 +665,26 @@ class ShortcutPackage extends ShortcutPackageItem {
boolean changed = false;
// For existing shortcuts, update timestamps if they have any resources.
// Also check if shortcuts' activities are still main activities. Otherwise, disable them.
if (!isNewApp) {
Resources publisherRes = null;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isDynamic()) {
if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) {
Slog.w(TAG, String.format(
"%s is no longer main activity. Disabling shorcut %s.",
getPackageName(), si.getId()));
if (disableDynamicWithId(si.getId())) {
continue; // Actually removed.
}
// Still pinned, so fall-through and possibly update the resources.
}
changed = true;
}
if (si.hasAnyResources()) {
if (!si.isOriginallyFromManifest()) {
if (publisherRes == null) {
@@ -912,9 +942,8 @@ class ShortcutPackage extends ShortcutPackageItem {
final ComponentName newActivity = newShortcut.getActivity();
if (newActivity == null) {
if (operation != ShortcutService.OPERATION_UPDATE) {
// This method may be called before validating shortcuts, so this may happen,
// and is a caller side error.
throw new NullPointerException("Activity must be provided");
service.wtf("Activity must not be null at this point");
continue; // Just ignore this invalid case.
}
continue; // Activity can be null for update.
}

View File

@@ -21,6 +21,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -58,18 +59,36 @@ public class ShortcutParser {
@Nullable
public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException {
final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId);
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Scanning package %s for manifest shortcuts on user %d",
packageName, userId));
}
final List<ResolveInfo> activities = service.injectGetMainActivities(packageName, userId);
if (activities == null || activities.size() == 0) {
return null;
}
List<ShortcutInfo> result = null;
try {
if (pi != null && pi.activities != null) {
for (ActivityInfo activityInfo : pi.activities) {
result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
final int size = activities.size();
for (int i = 0; i < size; i++) {
final ActivityInfo activityInfoNoMetadata = activities.get(i).activityInfo;
if (activityInfoNoMetadata == null) {
continue;
}
final ActivityInfo activityInfoWithMetadata =
service.injectGetActivityInfoWithMetadata(
activityInfoNoMetadata.getComponentName(), userId);
if (activityInfoWithMetadata != null) {
result = parseShortcutsOneFile(
service, activityInfoWithMetadata, packageName, userId, result);
}
}
} catch (RuntimeException e) {
// Resource ID mismatch may cause various runtime exceptions when parsing XMLs.
// Resource ID mismatch may cause various runtime exceptions when parsing XMLs,
// But we don't crash the device, so just swallow them.
service.wtf(
"Exception caught while parsing shortcut XML for package=" + packageName, e);
return null;
@@ -81,6 +100,11 @@ public class ShortcutParser {
ShortcutService service,
ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
List<ShortcutInfo> result) throws IOException, XmlPullParserException {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format(
"Checking main activity %s", activityInfo.getComponentName()));
}
XmlResourceParser parser = null;
try {
parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
@@ -223,7 +247,7 @@ public class ShortcutParser {
continue;
}
Log.w(TAG, "Unknown tag " + tag + " at depth " + depth);
ShortcutService.warnForInvalidTag(depth, tag);
}
} finally {
if (parser != null) {

View File

@@ -60,6 +60,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -76,6 +77,7 @@ import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TypedValue;
import android.util.Xml;
import android.view.IWindowManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -129,7 +131,7 @@ import java.util.function.Predicate;
* - Default launcher check does take a few ms. Worth caching.
*
* - Detect when already registered instances are passed to APIs again, which might break
* internal bitmap handling.
* internal bitmap handling.
*
* - Add more call stats.
*/
@@ -181,6 +183,8 @@ public class ShortcutService extends IShortcutService.Stub {
private static final String ATTR_VALUE = "value";
private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER;
@VisibleForTesting
interface ConfigConstants {
/**
@@ -282,7 +286,8 @@ public class ShortcutService extends IShortcutService.Stub {
private List<Integer> mDirtyUserIds = new ArrayList<>();
/**
* A counter that increments every time the system locale changes. We keep track of it to reset
* A counter that increments every time the system locale changes. We keep track of it to
* reset
* throttling counters on the first call from each package after the last locale change.
*
* We need this mechanism because we can't do much in the locale change callback, which is
@@ -294,8 +299,8 @@ public class ShortcutService extends IShortcutService.Stub {
private static final int PACKAGE_MATCH_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
// Stats
@VisibleForTesting
@@ -306,13 +311,15 @@ public class ShortcutService extends IShortcutService.Stub {
int GET_APPLICATION_INFO = 3;
int LAUNCHER_PERMISSION_CHECK = 4;
int CLEANUP_DANGLING_BITMAPS = 5;
int GET_ACTIVITIES_WITH_METADATA = 6;
int GET_ACTIVITY_WITH_METADATA = 6;
int GET_INSTALLED_PACKAGES = 7;
int CHECK_PACKAGE_CHANGES = 8;
int GET_APPLICATION_RESOURCES = 9;
int RESOURCE_NAME_LOOKUP = 10;
int GET_LAUNCHER_ACTIVITY = 11;
int CHECK_LAUNCHER_ACTIVITY = 12;
int COUNT = RESOURCE_NAME_LOOKUP + 1;
int COUNT = CHECK_LAUNCHER_ACTIVITY + 1;
}
final Object mStatLock = new Object();
@@ -335,9 +342,10 @@ public class ShortcutService extends IShortcutService.Stub {
OPERATION_SET,
OPERATION_ADD,
OPERATION_UPDATE
})
})
@Retention(RetentionPolicy.SOURCE)
@interface ShortcutOperation {}
@interface ShortcutOperation {
}
public ShortcutService(Context context) {
this(context, BackgroundThread.get().getLooper());
@@ -373,18 +381,22 @@ public class ShortcutService extends IShortcutService.Stub {
}
final private IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState) throws RemoteException {
@Override
public void onUidStateChanged(int uid, int procState) throws RemoteException {
handleOnUidStateChanged(uid, procState);
}
@Override public void onUidGone(int uid) throws RemoteException {
@Override
public void onUidGone(int uid) throws RemoteException {
handleOnUidStateChanged(uid, ActivityManager.MAX_PROCESS_STATE);
}
@Override public void onUidActive(int uid) throws RemoteException {
@Override
public void onUidActive(int uid) throws RemoteException {
}
@Override public void onUidIdle(int uid) throws RemoteException {
@Override
public void onUidIdle(int uid) throws RemoteException {
}
};
@@ -555,11 +567,11 @@ public class ShortcutService extends IShortcutService.Stub {
final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
? (int) parser.getLong(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
DEFAULT_MAX_ICON_DIMENSION_LOWRAM_DP)
: (int) parser.getLong(
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
DEFAULT_MAX_ICON_DIMENSION_DP));
ConfigConstants.KEY_MAX_ICON_DIMENSION_DP,
DEFAULT_MAX_ICON_DIMENSION_DP));
mMaxIconDimension = injectDipToPixel(iconDimensionDp);
@@ -777,7 +789,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
} catch (FileNotFoundException e) {
// Use the default
} catch (IOException|XmlPullParserException e) {
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
mRawLastResetTime = 0;
@@ -800,7 +812,7 @@ public class ShortcutService extends IShortcutService.Stub {
saveUserInternalLocked(userId, os, /* forBackup= */ false);
file.finishWrite(os);
} catch (XmlPullParserException|IOException e) {
} catch (XmlPullParserException | IOException e) {
Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
file.failWrite(os);
}
@@ -850,10 +862,10 @@ public class ShortcutService extends IShortcutService.Stub {
return null;
}
try {
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
cleanupDanglingBitmapDirectoriesLocked(userId, ret);
return ret;
} catch (IOException|XmlPullParserException e) {
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
return null;
} finally {
@@ -1133,7 +1145,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
final String baseName = String.valueOf(injectCurrentTimeMillis());
for (int suffix = 0;; suffix++) {
for (int suffix = 0; ; suffix++) {
final String filename = (suffix == 0 ? baseName : baseName + "_" + suffix) + ".png";
final File file = new File(packagePath, filename);
if (!file.exists()) {
@@ -1205,7 +1217,7 @@ public class ShortcutService extends IShortcutService.Stub {
} finally {
IoUtils.closeQuietly(out);
}
} catch (IOException|RuntimeException e) {
} catch (IOException | RuntimeException e) {
// STOPSHIP Change wtf to e
Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
if (path != null && path.exists()) {
@@ -1284,7 +1296,7 @@ public class ShortcutService extends IShortcutService.Stub {
private boolean isCallerSystem() {
final int callingUid = injectBinderCallingUid();
return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
return UserHandle.isSameApp(callingUid, Process.SYSTEM_UID);
}
private boolean isCallerShell() {
@@ -1349,7 +1361,7 @@ public class ShortcutService extends IShortcutService.Stub {
/**
* @throws IllegalArgumentException if {@code numShortcuts} is bigger than
* {@link #getMaxActivityShortcuts()}.
* {@link #getMaxActivityShortcuts()}.
*/
void enforceMaxActivityShortcuts(int numShortcuts) {
if (numShortcuts > mMaxShortcuts) {
@@ -1402,7 +1414,7 @@ public class ShortcutService extends IShortcutService.Stub {
* Clean up / validate an incoming shortcut.
* - Make sure all mandatory fields are set.
* - Make sure the intent's extras are persistable, and them to set
* {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras.
* {@link ShortcutInfo#mIntentPersistableExtras}. Also clear its extras.
* - Clear flags.
*
* TODO Detailed unit tests
@@ -1412,11 +1424,15 @@ public class ShortcutService extends IShortcutService.Stub {
if (shortcut.getActivity() != null) {
Preconditions.checkState(
shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
"Activity package name mismatch");
"Cannot publish shortcut: activity " + shortcut.getActivity() + " does not"
+ " belong to package " + shortcut.getPackage());
}
if (!forUpdate) {
shortcut.enforceMandatoryFields();
Preconditions.checkArgument(
injectIsMainActivity(shortcut.getActivity(), shortcut.getUserId()),
"Cannot publish shortcut: " + shortcut.getActivity() + " is not main activity");
}
if (shortcut.getIcon() != null) {
ShortcutInfo.validateIcon(shortcut.getIcon());
@@ -1425,6 +1441,26 @@ public class ShortcutService extends IShortcutService.Stub {
shortcut.replaceFlags(0);
}
/**
* When a shortcut has no target activity, set the default one from the package.
*/
private void fillInDefaultActivity(List<ShortcutInfo> shortcuts) {
ComponentName defaultActivity = null;
for (int i = shortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = shortcuts.get(i);
if (si.getActivity() == null) {
if (defaultActivity == null) {
defaultActivity = injectGetDefaultMainActivity(
si.getPackage(), si.getUserId());
Preconditions.checkState(defaultActivity != null,
"Launcher activity not found for package " + si.getPackage());
}
si.setActivity(defaultActivity);
}
}
}
private void assignImplicitRanks(List<ShortcutInfo> shortcuts) {
for (int i = shortcuts.size() - 1; i >= 0; i--) {
shortcuts.get(i).setImplicitRank(i);
@@ -1446,6 +1482,8 @@ public class ShortcutService extends IShortcutService.Stub {
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
fillInDefaultActivity(newShortcuts);
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_SET);
// Throttling.
@@ -1493,6 +1531,9 @@ public class ShortcutService extends IShortcutService.Stub {
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
// For update, don't fill in the default activity. Having null activity means
// "don't update the activity" here.
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_UPDATE);
// Throttling.
@@ -1573,6 +1614,8 @@ public class ShortcutService extends IShortcutService.Stub {
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
fillInDefaultActivity(newShortcuts);
ps.enforceShortcutCountsBeforeOperation(newShortcuts, OPERATION_ADD);
// Initialize the implicit ranks for ShortcutPackage.adjustRanks().
@@ -1795,7 +1838,8 @@ public class ShortcutService extends IShortcutService.Stub {
}
/**
* Reset all throttling, for developer options and command line. Only system/shell can call it.
* Reset all throttling, for developer options and command line. Only system/shell can call
* it.
*/
@Override
public void resetThrottling() {
@@ -1917,10 +1961,12 @@ public class ShortcutService extends IShortcutService.Stub {
// === House keeping ===
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId) {
private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
boolean appStillExists) {
synchronized (mLock) {
forEachLoadedUserLocked(user ->
cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
cleanUpPackageLocked(packageName, user.getUserId(), packageUserId,
appStillExists));
}
}
@@ -1932,7 +1978,8 @@ public class ShortcutService extends IShortcutService.Stub {
* This is called when an app is uninstalled, or an app gets "clear data"ed.
*/
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId,
boolean appStillExists) {
final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
final ShortcutUser user = getUserShortcutsLocked(owningUserId);
@@ -1961,6 +2008,13 @@ public class ShortcutService extends IShortcutService.Stub {
notifyListeners(packageName, owningUserId);
}
// If the app still exists (i.e. data cleared), we need to re-publish manifest shortcuts.
if (appStillExists && (packageUserId == owningUserId)) {
// This will do the notification and save when needed, so do it after the above
// notifyListeners.
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ true);
}
if (!wasUserLoaded) {
// Note this will execute the scheduled save.
unloadUserLocked(owningUserId);
@@ -2269,19 +2323,29 @@ public class ShortcutService extends IShortcutService.Stub {
public void onPackageDataCleared(String packageName, int uid) {
handlePackageDataCleared(packageName, getChangingUserId());
}
@Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
handlePackageChanged(packageName, getChangingUserId());
return false; // We don't need to receive onSomePackagesChanged(), so just false.
}
};
/**
* Called when a user is unlocked.
* - Check all known packages still exist, and otherwise perform cleanup.
* - If a package still exists, check the version code. If it's been updated, may need to
* update timestamps of its shortcuts.
* update timestamps of its shortcuts.
*/
@VisibleForTesting
void checkPackageChanges(@UserIdInt int ownerUserId) {
if (DEBUG) {
Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
}
if (injectIsSafeModeEnabled()) {
Slog.i(TAG, "Safe mode, skipping checkPackageChanges()");
return;
}
final long start = injectElapsedRealtime();
try {
@@ -2302,14 +2366,15 @@ public class ShortcutService extends IShortcutService.Stub {
if (gonePackages.size() > 0) {
for (int i = gonePackages.size() - 1; i >= 0; i--) {
final PackageWithUser pu = gonePackages.get(i);
cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId,
/* appStillExists = */ false);
}
}
final long now = injectCurrentTimeMillis();
// Then for each installed app, publish manifest shortcuts when needed.
forUpdatedPackages(ownerUserId, user.getLastAppScanTime(), ai -> {
user.handlePackageAddedOrUpdated(ai.packageName);
user.handlePackageAddedOrUpdated(ai.packageName, /* forceRescan=*/ false);
});
// Write the time just before the scan, because there may be apps that have just
@@ -2330,7 +2395,7 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
user.handlePackageAddedOrUpdated(packageName);
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
}
verifyStates();
}
@@ -2345,7 +2410,7 @@ public class ShortcutService extends IShortcutService.Stub {
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
if (isPackageInstalled(packageName, userId)) {
user.handlePackageAddedOrUpdated(packageName);
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ false);
}
}
verifyStates();
@@ -2356,7 +2421,7 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
packageUserId));
}
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ false);
verifyStates();
}
@@ -2366,7 +2431,23 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, String.format("handlePackageDataCleared: %s user=%d", packageName,
packageUserId));
}
cleanUpPackageForAllLoadedUsers(packageName, packageUserId);
cleanUpPackageForAllLoadedUsers(packageName, packageUserId, /* appStillExists = */ true);
verifyStates();
}
private void handlePackageChanged(String packageName, int packageUserId) {
if (DEBUG) {
Slog.d(TAG, String.format("handlePackageChanged: %s user=%d", packageName,
packageUserId));
}
// Activities may be disabled or enabled. Just rescan the package.
synchronized (mLock) {
final ShortcutUser user = getUserShortcutsLocked(packageUserId);
user.handlePackageAddedOrUpdated(packageName, /* forceRescan=*/ true);
}
verifyStates();
}
@@ -2405,7 +2486,7 @@ public class ShortcutService extends IShortcutService.Stub {
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
| (getSignatures ? PackageManager.GET_SIGNATURES : 0)
| (getSignatures ? PackageManager.GET_SIGNATURES : 0)
, userId);
} catch (RemoteException e) {
// Shouldn't happen.
@@ -2439,14 +2520,12 @@ public class ShortcutService extends IShortcutService.Stub {
}
@Nullable
@VisibleForTesting
PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity, @UserIdInt int userId) {
final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName,
PACKAGE_MATCH_FLAGS | PackageManager.GET_ACTIVITIES
| PackageManager.GET_META_DATA, userId);
return mIPackageManager.getActivityInfo(activity,
PACKAGE_MATCH_FLAGS | PackageManager.GET_META_DATA, userId);
} catch (RemoteException e) {
// Shouldn't happen.
Slog.wtf(TAG, "RemoteException", e);
@@ -2454,7 +2533,7 @@ public class ShortcutService extends IShortcutService.Stub {
} finally {
injectRestoreCallingIdentity(token);
logDurationStat(Stats.GET_ACTIVITIES_WITH_METADATA, start);
logDurationStat(Stats.GET_ACTIVITY_WITH_METADATA, start);
}
}
@@ -2531,6 +2610,86 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
private Intent getMainActivityIntent() {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(LAUNCHER_INTENT_CATEGORY);
return intent;
}
@Nullable
ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) {
final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
final Intent intent = getMainActivityIntent();
intent.setPackage(packageName);
final List<ResolveInfo> resolved =
mContext.getPackageManager().queryIntentActivitiesAsUser(
intent, PACKAGE_MATCH_FLAGS, userId);
return (resolved == null || resolved.size() == 0)
? null : resolved.get(0).activityInfo.getComponentName();
} finally {
injectRestoreCallingIdentity(token);
logDurationStat(Stats.GET_LAUNCHER_ACTIVITY, start);
}
}
boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
final Intent intent = getMainActivityIntent();
intent.setPackage(activity.getPackageName());
intent.setComponent(activity);
final List<ResolveInfo> resolved =
mContext.getPackageManager().queryIntentActivitiesAsUser(
intent, PACKAGE_MATCH_FLAGS, userId);
return resolved != null && resolved.size() > 0;
} finally {
injectRestoreCallingIdentity(token);
logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
}
}
@NonNull
List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) {
final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
final Intent intent = getMainActivityIntent();
intent.setPackage(packageName);
final List<ResolveInfo> resolved =
mContext.getPackageManager().queryIntentActivitiesAsUser(
intent, PACKAGE_MATCH_FLAGS, userId);
return (resolved != null) ? resolved : new ArrayList<>(0);
} finally {
injectRestoreCallingIdentity(token);
logDurationStat(Stats.CHECK_LAUNCHER_ACTIVITY, start);
}
}
boolean injectIsSafeModeEnabled() {
final long token = injectClearCallingIdentity();
try {
return IWindowManager.Stub
.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE))
.isSafeModeEnabled();
} catch (RemoteException e) {
return false; // Shouldn't happen though.
} finally {
injectRestoreCallingIdentity(token);
}
}
// === Backup & restore ===
boolean shouldBackupApp(String packageName, int userId) {
@@ -2560,7 +2719,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
try {
saveUserInternalLocked(userId, os, /* forBackup */ true);
} catch (XmlPullParserException|IOException e) {
} catch (XmlPullParserException | IOException e) {
// Shouldn't happen.
Slog.w(TAG, "Backup failed.", e);
return null;
@@ -2579,7 +2738,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ByteArrayInputStream is = new ByteArrayInputStream(payload);
try {
user = loadUserInternal(userId, is, /* fromBackup */ true);
} catch (XmlPullParserException|IOException e) {
} catch (XmlPullParserException | IOException e) {
Slog.w(TAG, "Restoration failed.", e);
return;
}
@@ -2656,7 +2815,7 @@ public class ShortcutService extends IShortcutService.Stub {
pw.println(mResetInterval);
pw.print(" maxUpdatesPerInterval: ");
pw.println(mMaxUpdatesPerInterval);
pw.print(" maxDynamicShortcuts: ");
pw.print(" maxShortcutsPerActivity: ");
pw.println(mMaxShortcuts);
pw.println();
@@ -2670,11 +2829,13 @@ public class ShortcutService extends IShortcutService.Stub {
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
dumpStatLS(pw, p, Stats.GET_ACTIVITIES_WITH_METADATA, "getActivities+metadata");
dumpStatLS(pw, p, Stats.GET_ACTIVITY_WITH_METADATA, "getActivity+metadata");
dumpStatLS(pw, p, Stats.GET_INSTALLED_PACKAGES, "getInstalledPackages");
dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
dumpStatLS(pw, p, Stats.GET_APPLICATION_RESOURCES, "getApplicationResources");
dumpStatLS(pw, p, Stats.RESOURCE_NAME_LOOKUP, "resourceNameLookup");
dumpStatLS(pw, p, Stats.GET_LAUNCHER_ACTIVITY, "getLauncherActivity");
dumpStatLS(pw, p, Stats.CHECK_LAUNCHER_ACTIVITY, "checkLauncherActivity");
}
for (int i = 0; i < mUsers.size(); i++) {
@@ -2725,7 +2886,9 @@ public class ShortcutService extends IShortcutService.Stub {
enforceShell();
(new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
resultReceiver.send(status, null);
}
static class CommandException extends Exception {
@@ -2796,6 +2959,9 @@ public class ShortcutService extends IShortcutService.Stub {
case "clear-shortcuts":
handleClearShortcuts();
break;
case "verify-states": // hidden command to verify various internal states.
handleVerifyStates();
break;
default:
return handleDefaultCommands(cmd);
}
@@ -2938,7 +3104,16 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName);
ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId);
ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId,
/* appStillExists = */ true);
}
private void handleVerifyStates() throws CommandException {
try {
verifyStatesForce(); // This will throw when there's an issue.
} catch (Throwable th) {
throw new CommandException(th.getMessage() + "\n" + Log.getStackTraceString(th));
}
}
}
@@ -2978,7 +3153,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
final void wtf(String message) {
wtf( message, /* exception= */ null);
wtf(message, /* exception= */ null);
}
// Injection point.
@@ -3092,6 +3267,10 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
private final void verifyStatesForce() {
verifyStatesInner();
}
private void verifyStatesInner() {
synchronized (this) {
forEachLoadedUserLocked(u -> u.forAllPackageItems(ShortcutPackageItem::verifyStates));

View File

@@ -88,7 +88,7 @@ class ShortcutUser {
@Override
public String toString() {
return String.format("{Package: %d, %s}", userId, packageName);
return String.format("[Package: %d, %s]", userId, packageName);
}
}
@@ -99,8 +99,6 @@ class ShortcutUser {
private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
private final SparseArray<ShortcutPackage> mPackagesFromUid = new SparseArray<>();
private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
/** Default launcher that can access the launcher apps APIs. */
@@ -244,12 +242,12 @@ class ShortcutUser {
}
}
public void handlePackageAddedOrUpdated(@NonNull String packageName) {
public void handlePackageAddedOrUpdated(@NonNull String packageName, boolean forceRescan) {
final boolean isNewApp = !mPackages.containsKey(packageName);
final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) {
if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp, forceRescan)) {
if (isNewApp) {
mPackages.remove(packageName);
}
@@ -381,8 +379,10 @@ class ShortcutUser {
pw.print(mUserId);
pw.print(" Known locale seq#: ");
pw.print(mKnownLocaleChangeSequenceNumber);
pw.print(" Last app scan: ");
pw.print(" Last app scan: [");
pw.print(mLastAppScanTime);
pw.print("] ");
pw.print(ShortcutService.formatTime(mLastAppScanTime));
pw.println();
prefix += prefix + " ";

View File

@@ -52,6 +52,7 @@ import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.pm.ShortcutServiceInternal;
@@ -98,8 +99,11 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
protected static final String TAG = "ShortcutManagerTest";
@@ -114,6 +118,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
protected static final String[] EMPTY_STRINGS = new String[0]; // Just for readability.
protected static final String MAIN_ACTIVITY_CLASS = "MainActivity";
// public for mockito
public class BaseContext extends MockContext {
@Override
@@ -311,8 +317,55 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
@Override
PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
return mContext.injectGetActivitiesWithMetadata(packageName, userId);
ActivityInfo injectGetActivityInfoWithMetadata(ComponentName activity,
@UserIdInt int userId) {
final PackageInfo pi = mContext.injectGetActivitiesWithMetadata(
activity.getPackageName(), userId);
if (pi == null || pi.activities == null) {
return null;
}
for (ActivityInfo ai : pi.activities) {
if (!mEnabledActivityChecker.test(ai.getComponentName(), userId)) {
continue;
}
if (activity.equals(ai.getComponentName())) {
return ai;
}
}
return null;
}
@Override
boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) {
if (!mEnabledActivityChecker.test(activity, userId)) {
return false;
}
return mMainActivityChecker.test(activity, userId);
}
@Override
List<ResolveInfo> injectGetMainActivities(@NonNull String packageName, int userId) {
final PackageInfo pi = mContext.injectGetActivitiesWithMetadata(
packageName, userId);
if (pi == null || pi.activities == null) {
return null;
}
final ArrayList<ResolveInfo> ret = new ArrayList<>(pi.activities.length);
for (int i = 0; i < pi.activities.length; i++) {
if (!mEnabledActivityChecker.test(pi.activities[i].getComponentName(), userId)) {
continue;
}
final ResolveInfo ri = new ResolveInfo();
ri.activityInfo = pi.activities[i];
ret.add(ri);
}
return ret;
}
@Override
ComponentName injectGetDefaultMainActivity(@NonNull String packageName, int userId) {
return mMainActivityFetcher.apply(packageName, userId);
}
@Override
@@ -334,6 +387,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
}
@Override
boolean injectIsSafeModeEnabled() {
return mSafeMode;
}
@Override
void wtf(String message, Exception e) {
// During tests, WTF is fatal.
@@ -441,6 +499,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
protected File mInjectedFilePathRoot;
protected boolean mSafeMode;
protected long mInjectedCurrentTimeMillis;
protected boolean mInjectedIsLowRamDevice;
@@ -512,6 +572,15 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
LAUNCHER_1.equals(callingPackage) || LAUNCHER_2.equals(callingPackage)
|| LAUNCHER_3.equals(callingPackage) || LAUNCHER_4.equals(callingPackage);
protected BiPredicate<ComponentName, Integer> mMainActivityChecker =
(activity, userId) -> true;
protected BiFunction<String, Integer, ComponentName> mMainActivityFetcher =
(packageName, userId) -> new ComponentName(packageName, MAIN_ACTIVITY_CLASS);
protected BiPredicate<ComponentName, Integer> mEnabledActivityChecker
= (activity, userId) -> true; // all activities are enabled.
protected static final long START_TIME = 1440000000101L;
protected static final long INTERVAL = 10000;
@@ -1051,10 +1120,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
+ "/" + ShortcutService.FILENAME_USER_PACKAGES, message);
}
protected void waitOnMainThread() throws Throwable {
runTestOnUiThread(() -> {});
}
/**
* Make a shortcut with an ID.
*/
@@ -1410,6 +1475,13 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
return i;
}
protected Intent genPackageChangedIntent(String pakcageName, int userId) {
Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED);
i.setData(Uri.parse("package:" + pakcageName));
i.putExtra(Intent.EXTRA_USER_HANDLE, userId);
return i;
}
protected Intent genPackageDataClear(String packageName, int userId) {
Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
i.setData(Uri.parse("package:" + packageName));

View File

@@ -40,6 +40,7 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicShortcutCountExceeded;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertEmpty;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertForLauncherCallback;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.filterByActivity;
@@ -50,6 +51,7 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.pfdToBitmap;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAll;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.waitOnMainThread;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
@@ -84,6 +86,7 @@ import com.android.frameworks.servicestests.R;
import com.android.server.pm.ShortcutService.ConfigConstants;
import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
import com.android.server.pm.ShortcutUser.PackageWithUser;
import com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.ShortcutListAsserter;
import org.mockito.ArgumentCaptor;
@@ -91,6 +94,9 @@ import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Tests for ShortcutService and ShortcutManager.
@@ -347,6 +353,127 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
public void testPublishWithNoActivity() {
// If activity is not explicitly set, use the default one.
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
// s1 and s3 has no activities.
final ShortcutInfo si1 = new ShortcutInfo.Builder(mClientContext, "si1")
.setShortLabel("label1")
.setIntent(new Intent("action1"))
.build();
final ShortcutInfo si2 = new ShortcutInfo.Builder(mClientContext, "si2")
.setShortLabel("label2")
.setActivity(new ComponentName(getCallingPackage(), "abc"))
.setIntent(new Intent("action2"))
.build();
final ShortcutInfo si3 = new ShortcutInfo.Builder(mClientContext, "si3")
.setShortLabel("label3")
.setIntent(new Intent("action3"))
.build();
// Set test 1
assertTrue(mManager.setDynamicShortcuts(list(si1)));
assertWith(getCallerShortcuts())
.haveIds("si1")
.forShortcutWithId("si1", si -> {
assertEquals(new ComponentName(getCallingPackage(),
MAIN_ACTIVITY_CLASS), si.getActivity());
});
// Set test 2
assertTrue(mManager.setDynamicShortcuts(list(si2, si1)));
assertWith(getCallerShortcuts())
.haveIds("si1", "si2")
.forShortcutWithId("si1", si -> {
assertEquals(new ComponentName(getCallingPackage(),
MAIN_ACTIVITY_CLASS), si.getActivity());
})
.forShortcutWithId("si2", si -> {
assertEquals(new ComponentName(getCallingPackage(),
"abc"), si.getActivity());
});
// Set test 3
assertTrue(mManager.setDynamicShortcuts(list(si3, si1)));
assertWith(getCallerShortcuts())
.haveIds("si1", "si3")
.forShortcutWithId("si1", si -> {
assertEquals(new ComponentName(getCallingPackage(),
MAIN_ACTIVITY_CLASS), si.getActivity());
})
.forShortcutWithId("si3", si -> {
assertEquals(new ComponentName(getCallingPackage(),
MAIN_ACTIVITY_CLASS), si.getActivity());
});
mInjectedCurrentTimeMillis += INTERVAL; // reset throttling
// Add test 1
mManager.removeAllDynamicShortcuts();
assertTrue(mManager.addDynamicShortcuts(list(si1)));
assertWith(getCallerShortcuts())
.haveIds("si1")
.forShortcutWithId("si1", si -> {
assertEquals(new ComponentName(getCallingPackage(),
MAIN_ACTIVITY_CLASS), si.getActivity());
});
// Add test 2
mManager.removeAllDynamicShortcuts();
assertTrue(mManager.addDynamicShortcuts(list(si2, si1)));
assertWith(getCallerShortcuts())
.haveIds("si1", "si2")
.forShortcutWithId("si1", si -> {
assertEquals(new ComponentName(getCallingPackage(),
MAIN_ACTIVITY_CLASS), si.getActivity());
})
.forShortcutWithId("si2", si -> {
assertEquals(new ComponentName(getCallingPackage(),
"abc"), si.getActivity());
});
// Add test 3
mManager.removeAllDynamicShortcuts();
assertTrue(mManager.addDynamicShortcuts(list(si3, si1)));
assertWith(getCallerShortcuts())
.haveIds("si1", "si3")
.forShortcutWithId("si1", si -> {
assertEquals(new ComponentName(getCallingPackage(),
MAIN_ACTIVITY_CLASS), si.getActivity());
})
.forShortcutWithId("si3", si -> {
assertEquals(new ComponentName(getCallingPackage(),
MAIN_ACTIVITY_CLASS), si.getActivity());
});
});
}
public void testPublishWithNoActivity_noMainActivityInPackage() {
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
final ShortcutInfo si1 = new ShortcutInfo.Builder(mClientContext, "si1")
.setShortLabel("label1")
.setIntent(new Intent("action1"))
.build();
// Returning null means there's no main activity in this package.
mMainActivityFetcher = (packageName, userId) -> null;
assertExpectException(
RuntimeException.class, "Launcher activity not found for", () -> {
assertTrue(mManager.setDynamicShortcuts(list(si1)));
});
});
}
public void testDeleteDynamicShortcuts() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
final ShortcutInfo si2 = makeShortcut("shortcut2");
@@ -2336,151 +2463,87 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
+ ConfigConstants.KEY_MAX_SHORTCUTS + "=99999999"
);
LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
setCaller(LAUNCHER_1, USER_0);
// Set listeners
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper()));
});
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
waitOnMainThread();
ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.haveIds("s1", "s2", "s3")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// From different package.
reset(c0);
runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_2),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_2, HANDLE_USER_0)
.haveIds("s1", "s2", "s3")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// Different user, callback shouldn't be called.
reset(c0);
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
waitOnMainThread();
verify(c0, times(0)).onShortcutsChanged(
anyString(),
any(List.class),
any(UserHandle.class)
);
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
}).assertNoCallbackCalled();
// Test for addDynamicShortcuts.
reset(c0);
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
dumpsysOnLogcat("before addDynamicShortcuts");
assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
});
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.addDynamicShortcuts(list(makeShortcut("s4"))));
});
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.haveIds("s1", "s2", "s3", "s4")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// Test for remove
reset(c0);
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
mManager.removeDynamicShortcuts(list("s1"));
});
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
mManager.removeDynamicShortcuts(list("s1"));
});
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.haveIds("s2", "s3", "s4")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// Test for update
reset(c0);
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
assertTrue(mManager.updateShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"))));
});
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.updateShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"))));
});
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
// All remaining shortcuts will be passed regardless of what's been updated.
.haveIds("s2", "s3", "s4")
.areAllWithKeyFieldsOnly()
.areAllDynamic();
// Test for deleteAll
reset(c0);
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
mManager.removeAllDynamicShortcuts();
});
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
mManager.removeAllDynamicShortcuts();
});
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.isEmpty();
// Update package1 with manifest shortcuts
reset(c0);
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_2);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
assertForLauncherCallback(mLauncherApps, () -> {
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_2);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.areAllManifest()
.areAllWithKeyFieldsOnly()
.haveIds("ms1", "ms2");
@@ -2518,58 +2581,42 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
reset(c0); // Check the callback for the next API call.
runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
mManager.removeDynamicShortcuts(list("s2"));
assertForLauncherCallback(mLauncherApps, () -> {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
mManager.removeDynamicShortcuts(list("s2"));
assertWith(getCallerShortcuts())
.haveIds("ms2", "s1", "s2")
assertWith(getCallerShortcuts())
.haveIds("ms2", "s1", "s2")
.selectByIds("ms2")
.areAllNotManifest()
.areAllPinned()
.areAllImmutable()
.areAllDisabled()
.selectByIds("ms2")
.areAllNotManifest()
.areAllPinned()
.areAllImmutable()
.areAllDisabled()
.revertToOriginalList()
.selectByIds("s1")
.areAllDynamic()
.areAllNotPinned()
.areAllEnabled()
.revertToOriginalList()
.selectByIds("s1")
.areAllDynamic()
.areAllNotPinned()
.areAllEnabled()
.revertToOriginalList()
.selectByIds("s2")
.areAllNotDynamic()
.areAllPinned()
.areAllEnabled()
;
});
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
.revertToOriginalList()
.selectByIds("s2")
.areAllNotDynamic()
.areAllPinned()
.areAllEnabled()
;
});
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
.haveIds("ms2", "s1", "s2")
.areAllWithKeyFieldsOnly();
// Remove CALLING_PACKAGE_2
reset(c0);
uninstallPackage(USER_0, CALLING_PACKAGE_2);
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0);
// Should get a callback with an empty list.
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_2),
shortcuts.capture(),
eq(HANDLE_USER_0)
);
assertWith(shortcuts.getValue())
assertForLauncherCallback(mLauncherApps, () -> {
uninstallPackage(USER_0, CALLING_PACKAGE_2);
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_0, USER_0,
/* appStillExists = */ false);
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_2, HANDLE_USER_0)
.isEmpty();
}
@@ -2970,7 +3017,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Nonexistent package.
uninstallPackage(USER_0, "abc");
mService.cleanUpPackageLocked("abc", USER_0, USER_0);
mService.cleanUpPackageLocked("abc", USER_0, USER_0, /* appStillExists = */ false);
// No changes.
assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
@@ -3002,7 +3049,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Remove a package.
uninstallPackage(USER_0, CALLING_PACKAGE_1);
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0);
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0,
/* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3033,7 +3081,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Remove a launcher.
uninstallPackage(USER_10, LAUNCHER_1);
mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10);
mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10, /* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3061,7 +3109,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Remove a package.
uninstallPackage(USER_10, CALLING_PACKAGE_2);
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10);
mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10,
/* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3089,7 +3138,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// Remove the other launcher from user 10 too.
uninstallPackage(USER_10, LAUNCHER_2);
mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10);
mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10,
/* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3117,7 +3167,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// More remove.
uninstallPackage(USER_10, CALLING_PACKAGE_1);
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10);
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10,
/* appStillExists = */ false);
assertEquals(set(CALLING_PACKAGE_2),
hashSet(user0.getAllPackagesForTest().keySet()));
@@ -3143,6 +3194,74 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mService.saveDirtyInfo();
}
public void testCleanupPackage_republishManifests() {
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_2);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
list("s2", "s3", "ms1", "ms2"), HANDLE_USER_0);
});
// Remove ms2 from manifest.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_1);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"))));
// Make sure the shortcuts are in the intended state.
assertWith(getCallerShortcuts())
.haveIds("ms1", "ms2", "s1", "s2", "s3")
.selectByIds("ms1")
.areAllManifest()
.areAllPinned()
.revertToOriginalList()
.selectByIds("ms2")
.areAllNotManifest()
.areAllPinned()
.revertToOriginalList()
.selectByIds("s1")
.areAllDynamic()
.areAllNotPinned()
.revertToOriginalList()
.selectByIds("s2")
.areAllDynamic()
.areAllPinned()
.revertToOriginalList()
.selectByIds("s3")
.areAllNotDynamic()
.areAllPinned();
});
// Clean up + re-publish manifests.
mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0,
/* appStillExists = */ true);
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest();
});
}
public void testHandleGonePackage_crossProfile() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3454,6 +3573,20 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertTrue(mManager.addDynamicShortcuts(list(
makeShortcutWithIcon("s1", bmp32x32), makeShortcutWithIcon("s2", bmp32x32)
)));
// Also add a manifest shortcut, which should be removed too.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_1);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1", "s2", "ms1")
.selectManifest()
.haveIds("ms1");
});
setCaller(CALLING_PACKAGE_2, USER_0);
assertTrue(mManager.addDynamicShortcuts(list(makeShortcutWithIcon("s1", bmp32x32))));
@@ -3629,8 +3762,47 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
}
public void testHandlePackageUpdate() throws Throwable {
public void testHandlePackageClearData_manifestRepublished() {
// Add two manifests and two dynamics.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_2);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.addDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"))));
});
runWithCaller(LAUNCHER_1, USER_10, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2", "s2"), HANDLE_USER_10);
});
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1", "ms2", "s1", "s2")
.areAllEnabled()
.selectPinned()
.haveIds("ms2", "s2");
});
// Clear data
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageDataClear(CALLING_PACKAGE_1, USER_10));
// Only manifest shortcuts will remain, and are no longer pinned.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1", "ms2")
.areAllEnabled()
.areAllNotPinned();
});
}
public void testHandlePackageUpdate() throws Throwable {
// Set up shortcuts and launchers.
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
@@ -3894,6 +4066,202 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
public void testHandlePackageChanged() {
final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1");
final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2");
addManifestShortcutResource(ACTIVITY1, R.xml.shortcut_1);
addManifestShortcutResource(ACTIVITY2, R.xml.shortcut_1_alt);
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.addDynamicShortcuts(list(
makeShortcutWithActivity("s1", ACTIVITY1),
makeShortcutWithActivity("s2", ACTIVITY2)
)));
});
runWithCaller(LAUNCHER_1, USER_10, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1-alt", "s2"), HANDLE_USER_10);
});
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1", "ms1-alt", "s1", "s2")
.areAllEnabled()
.selectPinned()
.haveIds("ms1-alt", "s2")
.revertToOriginalList()
.selectByIds("ms1", "s1")
.areAllWithActivity(ACTIVITY1)
.revertToOriginalList()
.selectByIds("ms1-alt", "s2")
.areAllWithActivity(ACTIVITY2)
;
});
// First, no changes.
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1", "ms1-alt", "s1", "s2")
.areAllEnabled()
.selectPinned()
.haveIds("ms1-alt", "s2")
.revertToOriginalList()
.selectByIds("ms1", "s1")
.areAllWithActivity(ACTIVITY1)
.revertToOriginalList()
.selectByIds("ms1-alt", "s2")
.areAllWithActivity(ACTIVITY2)
;
});
// Disable activity 1
mEnabledActivityChecker = (activity, userId) -> !ACTIVITY1.equals(activity);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1-alt", "s2")
.areAllEnabled()
.selectPinned()
.haveIds("ms1-alt", "s2")
.revertToOriginalList()
.selectByIds("ms1-alt", "s2")
.areAllWithActivity(ACTIVITY2)
;
});
// Re-enable activity 1.
// Manifest shortcuts will be re-published, but dynamic ones are not.
mEnabledActivityChecker = (activity, userId) -> true;
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1", "ms1-alt", "s2")
.areAllEnabled()
.selectPinned()
.haveIds("ms1-alt", "s2")
.revertToOriginalList()
.selectByIds("ms1")
.areAllWithActivity(ACTIVITY1)
.revertToOriginalList()
.selectByIds("ms1-alt", "s2")
.areAllWithActivity(ACTIVITY2)
;
});
// Disable activity 2
// Because "ms1-alt" and "s2" are both pinned, they will remain, but disabled.
mEnabledActivityChecker = (activity, userId) -> !ACTIVITY2.equals(activity);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1", "ms1-alt", "s2")
.selectDynamic().isEmpty().revertToOriginalList() // no dynamics.
.selectPinned()
.haveIds("ms1-alt", "s2")
.areAllDisabled()
.revertToOriginalList()
.selectByIds("ms1")
.areAllWithActivity(ACTIVITY1)
.areAllEnabled()
;
});
}
public void testHandlePackageUpdate_activityNoLongerMain() throws Throwable {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcutWithActivity("s1a",
new ComponentName(getCallingPackage(), "act1")),
makeShortcutWithActivity("s1b",
new ComponentName(getCallingPackage(), "act1")),
makeShortcutWithActivity("s2a",
new ComponentName(getCallingPackage(), "act2")),
makeShortcutWithActivity("s2b",
new ComponentName(getCallingPackage(), "act2")),
makeShortcutWithActivity("s3a",
new ComponentName(getCallingPackage(), "act3")),
makeShortcutWithActivity("s3b",
new ComponentName(getCallingPackage(), "act3"))
)));
assertWith(getCallerShortcuts())
.haveIds("s1a", "s1b", "s2a", "s2b", "s3a", "s3b")
.areAllDynamic();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
list("s1b", "s2b", "s3b"),
HANDLE_USER_0);
});
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1a", "s1b", "s2a", "s2b", "s3a", "s3b")
.areAllDynamic()
.selectByIds("s1b", "s2b", "s3b")
.areAllPinned();
});
// Update the app and act2 and act3 are no longer main.
mMainActivityChecker = (activity, userId) -> {
return activity.getClassName().equals("act1");
};
setCaller(LAUNCHER_1, USER_0);
assertForLauncherCallback(mLauncherApps, () -> {
updatePackageVersion(CALLING_PACKAGE_1, 1);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
}).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
// Make sure the launcher gets callbacks.
.haveIds("s1a", "s1b", "s2b", "s3b")
.areAllWithKeyFieldsOnly();
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
// s2a and s3a are gone, but s2b and s3b will remain because they're pinned, and
// disabled.
assertWith(getCallerShortcuts())
.haveIds("s1a", "s1b", "s2b", "s3b")
.selectByIds("s1a", "s1b")
.areAllDynamic()
.areAllEnabled()
.revertToOriginalList()
.selectByIds("s2b", "s3b")
.areAllNotDynamic()
.areAllDisabled()
.areAllPinned()
;
});
}
protected void prepareForBackupTest() {
prepareCrossProfileDataSet();

View File

@@ -72,18 +72,68 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
"ID must be provided",
() -> new ShortcutInfo.Builder(getTestContext()).build());
assertExpectException(NullPointerException.class, "Intent action must be set",
() -> new ShortcutInfo.Builder(getTestContext()).setIntent(new Intent()));
assertExpectException(NullPointerException.class, "Activity must be provided", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext()).setId("id").build();
assertTrue(getManager().setDynamicShortcuts(list(si)));
});
assertExpectException(
RuntimeException.class,
"id cannot be empty",
() -> new ShortcutInfo.Builder(getTestContext(), null));
assertExpectException(
RuntimeException.class,
"id cannot be empty",
() -> new ShortcutInfo.Builder(getTestContext(), ""));
assertExpectException(
RuntimeException.class,
"intent cannot be null",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(null));
assertExpectException(
RuntimeException.class,
"action must be set",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
assertExpectException(
RuntimeException.class,
"activity cannot be null",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setActivity(null));
assertExpectException(
RuntimeException.class,
"shortLabel cannot be empty",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(null));
assertExpectException(
RuntimeException.class,
"shortLabel cannot be empty",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setShortLabel(""));
assertExpectException(
RuntimeException.class,
"longLabel cannot be empty",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(null));
assertExpectException(
RuntimeException.class,
"longLabel cannot be empty",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setLongLabel(""));
assertExpectException(
RuntimeException.class,
"disabledMessage cannot be empty",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(null));
assertExpectException(
RuntimeException.class,
"disabledMessage cannot be empty",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setDisabledMessage(""));
assertExpectException(NullPointerException.class, "action must be set",
() -> new ShortcutInfo.Builder(getTestContext(), "id").setIntent(new Intent()));
// same for add.
assertExpectException(
IllegalArgumentException.class, "Short label must be provided", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
.setId("id")
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
.build();
assertTrue(getManager().setDynamicShortcuts(list(si)));
@@ -91,25 +141,24 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertExpectException(
IllegalArgumentException.class, "Short label must be provided", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
.setId("id")
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
.build();
assertTrue(getManager().addDynamicShortcuts(list(si)));
});
// same for add.
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
.setId("id")
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
.setShortLabel("x")
.build();
assertTrue(getManager().setDynamicShortcuts(list(si)));
});
// same for add.
assertExpectException(NullPointerException.class, "Intent must be provided", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
.setId("id")
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName(getTestContext().getPackageName(), "s"))
.setShortLabel("x")
.build();
@@ -117,18 +166,17 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
assertExpectException(
IllegalStateException.class, "package name mismatch", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
.setId("id")
IllegalStateException.class, "does not belong to package", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName("xxx", "s"))
.build();
assertTrue(getManager().setDynamicShortcuts(list(si)));
});
// same for add.
assertExpectException(
IllegalStateException.class, "package name mismatch", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext())
.setId("id")
IllegalStateException.class, "does not belong to package", () -> {
ShortcutInfo si = new ShortcutInfo.Builder(getTestContext(), "id")
.setActivity(new ComponentName("xxx", "s"))
.build();
assertTrue(getManager().addDynamicShortcuts(list(si)));

View File

@@ -24,9 +24,11 @@ import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -35,11 +37,14 @@ import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.Callback;
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.BaseBundle;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -54,6 +59,7 @@ import junit.framework.AssertionFailedError;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.io.BufferedReader;
@@ -69,6 +75,7 @@ import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -664,8 +671,8 @@ public class ShortcutManagerTestUtils {
}
private ShortcutListAsserter(ShortcutListAsserter original, List<ShortcutInfo> list) {
mOriginal = original == null ? this : original;
mList = new ArrayList<>(list);
mOriginal = (original == null) ? this : original;
mList = (list == null) ? new ArrayList<>(0) : new ArrayList<>(list);
}
public ShortcutListAsserter revertToOriginalList() {
@@ -813,6 +820,11 @@ public class ShortcutManagerTestUtils {
return this;
}
public ShortcutListAsserter areAllWithActivity(ComponentName activity) {
forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.getActivity().equals(activity)));
return this;
}
public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
boolean found = false;
for (int i = 0; i < mList.size(); i++) {
@@ -902,4 +914,69 @@ public class ShortcutManagerTestUtils {
}
}
}
public static void waitOnMainThread() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
new Handler(Looper.getMainLooper()).post(() -> latch.countDown());
latch.await();
}
public static class LauncherCallbackAsserter {
private final LauncherApps.Callback mCallback = mock(LauncherApps.Callback.class);
private Callback getMockCallback() {
return mCallback;
}
public LauncherCallbackAsserter assertNoCallbackCalled() {
verify(mCallback, times(0)).onShortcutsChanged(
anyString(),
any(List.class),
any(UserHandle.class));
return this;
}
public LauncherCallbackAsserter assertNoCallbackCalledForPackage(
String publisherPackageName) {
verify(mCallback, times(0)).onShortcutsChanged(
eq(publisherPackageName),
any(List.class),
any(UserHandle.class));
return this;
}
public LauncherCallbackAsserter assertNoCallbackCalledForPackageAndUser(
String publisherPackageName, UserHandle publisherUserHandle) {
verify(mCallback, times(0)).onShortcutsChanged(
eq(publisherPackageName),
any(List.class),
eq(publisherUserHandle));
return this;
}
public ShortcutListAsserter assertCallbackCalledForPackageAndUser(
String publisherPackageName, UserHandle publisherUserHandle) {
final ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class);
verify(mCallback, times(1)).onShortcutsChanged(
eq(publisherPackageName),
shortcuts.capture(),
eq(publisherUserHandle));
return new ShortcutListAsserter(shortcuts.getValue());
}
}
public static LauncherCallbackAsserter assertForLauncherCallback(
LauncherApps launcherApps, Runnable body) throws InterruptedException {
final LauncherCallbackAsserter asserter = new LauncherCallbackAsserter();
launcherApps.registerCallback(asserter.getMockCallback(),
new Handler(Looper.getMainLooper()));
body.run();
waitOnMainThread();
return asserter;
}
}