diff options
12 files changed, 816 insertions, 149 deletions
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl index 9c90346a075c..2ba24f6eecdb 100644 --- a/core/java/android/content/pm/IShortcutService.aidl +++ b/core/java/android/content/pm/IShortcutService.aidl @@ -49,6 +49,8 @@ interface IShortcutService { void resetThrottling(); // system only API for developer opsions + void onApplicationActive(String packageName, int userId); // system only API for sysUI + byte[] getBackupPayload(int user); void applyRestore(in byte[] payload, int user); diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 9b1d0f7d05bd..bd8cae20fc4b 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; @@ -35,8 +34,6 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; import java.util.Set; // TODO Enhance javadoc @@ -307,14 +304,6 @@ public final class ShortcutInfo implements Parcelable { case Icon.TYPE_RESOURCE: case Icon.TYPE_BITMAP: break; // OK - case Icon.TYPE_URI: - if (ContentResolver.SCHEME_CONTENT.equals(icon.getUri().getScheme())) { - break; - } - // Note "file:" is not supported, because depending on the path, system server - // cannot access it. // TODO Revisit "file:" icon support - - // fall through default: throw getInvalidIconException(); } @@ -374,6 +363,12 @@ public final class ShortcutInfo implements Parcelable { * Optionally sets the target activity. If it's not set, and if the caller application * has multiple launcher icons, this shortcut will be shown on all those icons. * If it's set, this shortcut will be only shown on this activity. + * + * <p>The package name of the target activity must match the package name of the shortcut + * publisher. + * + * <p>This has nothing to do with the activity that this shortcut will launch. This is + * a hint to the launcher app about which launcher icon to associate this shortcut with. */ @NonNull public Builder setActivityComponent(@NonNull ComponentName activityComponent) { @@ -385,11 +380,8 @@ public final class ShortcutInfo implements Parcelable { * Optionally sets an icon. * * <ul> - * <li>Tints are not supported. - * <li>Bitmaps, resources and "content:" URIs are supported. - * <li>"content:" URI will be fetched when a shortcut is registered to - * {@link ShortcutManager}. Changing the content from the same URI later will - * not be reflected to launcher icons. + * <li>Tints set by {@link Icon#setTint} or {@link Icon#setTintList} are not supported. + * <li>Bitmaps and resources are supported, but "content:" URIs are not supported. * </ul> * * <p>For performance reasons, icons will <b>NOT</b> be available on instances @@ -498,6 +490,11 @@ public final class ShortcutInfo implements Parcelable { /** * Return the target activity, which may be null, in which case the shortcut is not associated * with a specific activity. + * + * <p>This has nothing to do with the activity that this shortcut will launch. This is + * a hint to the launcher app that on which launcher icon this shortcut should be shown. + * + * @see Builder#setActivityComponent */ @Nullable public ComponentName getActivityComponent() { @@ -550,6 +547,10 @@ public final class ShortcutInfo implements Parcelable { * * <p>All shortcuts must have an intent, but this method will return null when * {@link #hasKeyFieldsOnly()} is true. + * + * <p>Launcher apps <b>cannot</b> see the intent. If a {@link ShortcutInfo} is obtained via + * {@link LauncherApps}, then this method will always return null. Launcher apps can only + * start a shortcut intent with {@link LauncherApps#startShortcut}. */ @Nullable public Intent getIntent() { diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index 75803d3631a3..ab0367dc0df4 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -62,13 +62,17 @@ import java.util.List; * <h3>Rate limiting</h3> * * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcuts(List)}, - * and {@link #updateShortcuts(List)} will be + * and {@link #updateShortcuts(List)} from <b>background applications</b> will be * rate-limited. An application can call these methods at most * {@link #getRemainingCallCount()} times until the rate-limiting counter is reset, - * which happens every hour. + * which happens at a certain time every day. * * <p>An application can use {@link #getRateLimitResetTime()} to get the next reset time. * + * <p>Foreground applications (i.e. ones with a foreground activity or a foreground services) + * will not be throttled. Also, when an application comes to foreground, + * {@link #getRemainingCallCount()} will be reset to the initial value. + * * <p>For testing purposes, use "Developer Options" (found in the Settings menu) to reset the * internal rate-limiting counter. Automated tests can use the following ADB shell command to * achieve the same effect:</p> diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index dc3d317533a6..3f8bad15035b 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -23,7 +23,6 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.LauncherApps.ShortcutQuery; import android.os.ParcelFileDescriptor; -import android.os.UserHandle; import java.util.List; @@ -68,4 +67,10 @@ public abstract class ShortcutServiceInternal { public abstract boolean hasShortcutHostPermission(int launcherUserId, @NonNull String callingPackage); + + /** + * Called by AM when the system locale changes *within the AM lock*. ABSOLUTELY do not take + * any locks in this method. + */ + public abstract void onSystemLocaleChangedNoLock(); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 22cf279d76b2..040da4d90453 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1989,6 +1989,11 @@ <permission android:name="android.permission.UPDATE_CONFIG" android:protectionLevel="signature|privileged" /> + <!-- Allows the system to reset throttling in shortcut manager. + @hide --> + <permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING" + android:protectionLevel="signature" /> + <!-- ========================================= --> <!-- Permissions for special development tools --> <!-- ========================================= --> diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2153a2cf3149..842a0e367b94 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -141,6 +141,7 @@ import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -18215,6 +18216,12 @@ public final class ActivityManagerService extends ActivityManagerNative null, AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) { + // Tell the shortcut manager that the system locale changed. It needs to know + // it before any other apps receive ACTION_LOCALE_CHANGED, which is why + // we "push" from here, rather than having the service listen to the broadcast. + LocalServices.getService(ShortcutServiceInternal.class) + .onSystemLocaleChangedNoLock(); + intent = new Intent(Intent.ACTION_LOCALE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); if (!mProcessesReady) { diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index c6d66fe7fee5..76d47a839f90 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -57,15 +57,18 @@ class ShortcutLauncher extends ShortcutPackageItem { */ final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>(); - private ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName, + private ShortcutLauncher(@NonNull ShortcutUser shortcutUser, + @UserIdInt int ownerUserId, @NonNull String packageName, @UserIdInt int launcherUserId, ShortcutPackageInfo spi) { - super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty()); + super(shortcutUser, launcherUserId, packageName, + spi != null ? spi : ShortcutPackageInfo.newEmpty()); mOwnerUserId = ownerUserId; } - public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName, + public ShortcutLauncher(@NonNull ShortcutUser shortcutUser, + @UserIdInt int ownerUserId, @NonNull String packageName, @UserIdInt int launcherUserId) { - this(ownerUserId, packageName, launcherUserId, null); + this(shortcutUser, ownerUserId, packageName, launcherUserId, null); } @Override @@ -179,8 +182,8 @@ class ShortcutLauncher extends ShortcutPackageItem { /** * Load. */ - public static ShortcutLauncher loadFromXml(XmlPullParser parser, int ownerUserId, - boolean fromBackup) throws IOException, XmlPullParserException { + public static ShortcutLauncher loadFromXml(XmlPullParser parser, ShortcutUser shortcutUser, + int ownerUserId, boolean fromBackup) throws IOException, XmlPullParserException { final String launcherPackageName = ShortcutService.parseStringAttribute(parser, ATTR_PACKAGE_NAME); @@ -189,8 +192,8 @@ class ShortcutLauncher extends ShortcutPackageItem { fromBackup ? ownerUserId : ShortcutService.parseIntAttribute(parser, ATTR_LAUNCHER_USER_ID, ownerUserId); - final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName, - launcherUserId); + final ShortcutLauncher ret = new ShortcutLauncher(shortcutUser, launcherUserId, + launcherPackageName, launcherUserId); ArraySet<String> ids = null; final int outerDepth = parser.getDepth(); diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index d7f8cc67a5f0..151f61e39192 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -38,7 +38,6 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.function.Predicate; @@ -95,12 +94,21 @@ class ShortcutPackage extends ShortcutPackageItem { */ private long mLastResetTime; - private ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) { - super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty()); + private final int mPackageUid; + + private long mLastKnownForegroundElapsedTime; + + private ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser, + int packageUserId, String packageName, ShortcutPackageInfo spi) { + super(shortcutUser, packageUserId, packageName, + spi != null ? spi : ShortcutPackageInfo.newEmpty()); + + mPackageUid = s.injectGetPackageUid(packageName, packageUserId); } - public ShortcutPackage(int packageUserId, String packageName) { - this(packageUserId, packageName, null); + public ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser, + int packageUserId, String packageName) { + this(s, shortcutUser, packageUserId, packageName, null); } @Override @@ -109,6 +117,10 @@ class ShortcutPackage extends ShortcutPackageItem { return getPackageUserId(); } + public int getPackageUid() { + return mPackageUid; + } + /** * Called when a shortcut is about to be published. At this point we know the publisher package * exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so @@ -274,18 +286,12 @@ class ShortcutPackage extends ShortcutPackageItem { } // Then, for the pinned set for each launcher, set the pin flag one by one. - final ArrayMap<ShortcutUser.PackageWithUser, ShortcutLauncher> launchers = - s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers(); - - for (int l = launchers.size() - 1; l >= 0; l--) { - // Note even if a launcher that hasn't been installed can still pin shortcuts. - - final ShortcutLauncher launcherShortcuts = launchers.valueAt(l); + s.getUserShortcutsLocked(getPackageUserId()).forAllLaunchers(launcherShortcuts -> { final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds( getPackageName(), getPackageUserId()); if (pinned == null || pinned.size() == 0) { - continue; + return; } for (int i = pinned.size() - 1; i >= 0; i--) { final String id = pinned.valueAt(i); @@ -299,7 +305,7 @@ class ShortcutPackage extends ShortcutPackageItem { } si.addFlags(ShortcutInfo.FLAG_PINNED); } - } + }); // Lastly, remove the ones that are no longer pinned nor dynamic. removeOrphans(s); @@ -307,8 +313,28 @@ class ShortcutPackage extends ShortcutPackageItem { /** * Number of calls that the caller has made, since the last reset. + * + * <p>This takes care of the resetting the counter for foreground apps as well as after + * locale changes. */ public int getApiCallCount(@NonNull ShortcutService s) { + mShortcutUser.resetThrottlingIfNeeded(s); + + // Reset the counter if: + // - the package is in foreground now. + // - the package is *not* in foreground now, but was in foreground at some point + // since the previous time it had been. + if (s.isUidForegroundLocked(mPackageUid) + || mLastKnownForegroundElapsedTime + < s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) { + mLastKnownForegroundElapsedTime = s.injectElapsedRealtime(); + resetRateLimiting(s); + } + + // Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount, + // but we just can't return 0 at this point, because we may have to update + // mLastResetTime. + final long last = s.getLastResetTimeLocked(); final long now = s.injectCurrentTimeMillis(); @@ -335,16 +361,30 @@ class ShortcutPackage extends ShortcutPackageItem { /** * If the caller app hasn't been throttled yet, increment {@link #mApiCallCount} * and return true. Otherwise just return false. + * + * <p>This takes care of the resetting the counter for foreground apps as well as after + * locale changes, which is done internally by {@link #getApiCallCount}. */ public boolean tryApiCall(@NonNull ShortcutService s) { if (getApiCallCount(s) >= s.mMaxUpdatesPerInterval) { return false; } mApiCallCount++; + s.scheduleSaveUser(getOwnerUserId()); return true; } - public void resetRateLimitingForCommandLine() { + public void resetRateLimiting(@NonNull ShortcutService s) { + if (ShortcutService.DEBUG) { + Slog.d(TAG, "resetRateLimiting: " + getPackageName()); + } + if (mApiCallCount > 0) { + mApiCallCount = 0; + s.scheduleSaveUser(getOwnerUserId()); + } + } + + public void resetRateLimitingForCommandLineNoSaving() { mApiCallCount = 0; mLastResetTime = 0; } @@ -451,6 +491,8 @@ class ShortcutPackage extends ShortcutPackageItem { pw.print(prefix); pw.print("Package: "); pw.print(getPackageName()); + pw.print(" UID: "); + pw.print(mPackageUid); pw.println(); pw.print(prefix); @@ -459,6 +501,13 @@ class ShortcutPackage extends ShortcutPackageItem { pw.print(getApiCallCount(s)); pw.println(); + // getApiCallCount() may have updated mLastKnownForegroundElapsedTime. + pw.print(prefix); + pw.print(" "); + pw.print("Last known FG: "); + pw.print(mLastKnownForegroundElapsedTime); + pw.println(); + // This should be after getApiCallCount(), which may update it. pw.print(prefix); pw.print(" "); @@ -571,14 +620,15 @@ class ShortcutPackage extends ShortcutPackageItem { out.endTag(null, TAG_SHORTCUT); } - public static ShortcutPackage loadFromXml(ShortcutService s, XmlPullParser parser, - int ownerUserId, boolean fromBackup) + public static ShortcutPackage loadFromXml(ShortcutService s, ShortcutUser shortcutUser, + XmlPullParser parser, boolean fromBackup) throws IOException, XmlPullParserException { final String packageName = ShortcutService.parseStringAttribute(parser, ATTR_NAME); - final ShortcutPackage ret = new ShortcutPackage(ownerUserId, packageName); + final ShortcutPackage ret = new ShortcutPackage(s, shortcutUser, + shortcutUser.getUserId(), packageName); ret.mDynamicShortcutCount = ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT); @@ -602,7 +652,8 @@ class ShortcutPackage extends ShortcutPackageItem { ret.getPackageInfo().loadFromXml(parser, fromBackup); continue; case TAG_SHORTCUT: - final ShortcutInfo si = parseShortcut(parser, packageName, ownerUserId); + final ShortcutInfo si = parseShortcut(parser, packageName, + shortcutUser.getUserId()); // Don't use addShortcut(), we don't need to save the icon. ret.mShortcuts.put(si.getId(), si); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index f31dd17d6288..6fbdb8221ac5 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -34,8 +34,12 @@ abstract class ShortcutPackageItem { private final ShortcutPackageInfo mPackageInfo; - protected ShortcutPackageItem(int packageUserId, @NonNull String packageName, + protected final ShortcutUser mShortcutUser; + + protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser, + int packageUserId, @NonNull String packageName, @NonNull ShortcutPackageInfo packageInfo) { + mShortcutUser = shortcutUser; mPackageUserId = packageUserId; mPackageName = Preconditions.checkStringNotEmpty(packageName); mPackageInfo = Preconditions.checkNotNull(packageInfo); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c0874efe211c..0f1780467335 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -19,9 +19,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityManagerNative; import android.app.AppGlobals; +import android.app.IUidObserver; import android.content.ComponentName; -import android.content.ContentProvider; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -39,11 +40,9 @@ import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; -import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.RectF; import android.graphics.drawable.Icon; -import android.net.Uri; import android.os.Binder; import android.os.Environment; import android.os.FileUtils; @@ -56,16 +55,18 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SELinux; import android.os.ShellCommand; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; import android.text.format.Time; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.KeyValueListParser; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; +import android.util.SparseLongArray; import android.util.TypedValue; import android.util.Xml; @@ -73,6 +74,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; @@ -102,6 +104,7 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Predicate; @@ -124,12 +127,13 @@ public class ShortcutService extends IShortcutService.Stub { static final boolean DEBUG = false; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true + static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true @VisibleForTesting - static final long DEFAULT_RESET_INTERVAL_SEC = 60 * 60; // 1 hour + static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day @VisibleForTesting - static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 2; + static final int DEFAULT_MAX_UPDATES_PER_INTERVAL = 10; @VisibleForTesting static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 5; @@ -162,6 +166,7 @@ public class ShortcutService extends IShortcutService.Stub { private static final String TAG_ROOT = "root"; private static final String TAG_LAST_RESET_TIME = "last_reset_time"; + private static final String TAG_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale_seq_no"; private static final String ATTR_VALUE = "value"; @@ -256,8 +261,23 @@ public class ShortcutService extends IShortcutService.Stub { private final UserManager mUserManager; @GuardedBy("mLock") + final SparseIntArray mUidState = new SparseIntArray(); + + @GuardedBy("mLock") + final SparseLongArray mUidLastForegroundElapsedTime = new SparseLongArray(); + + @GuardedBy("mLock") private List<Integer> mDirtyUserIds = new ArrayList<>(); + /** + * 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 + * {@link ShortcutServiceInternal#onSystemLocaleChangedNoLock()}. + */ + private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong(); + private static final int PACKAGE_MATCH_FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE @@ -283,6 +303,9 @@ public class ShortcutService extends IShortcutService.Stub { @GuardedBy("mStatLock") private final long[] mDurationStats = new long[Stats.COUNT]; + private static final int PROCESS_STATE_FOREGROUND_THRESHOLD = + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + public ShortcutService(Context context) { this(context, BackgroundThread.get().getLooper()); } @@ -297,6 +320,9 @@ public class ShortcutService extends IShortcutService.Stub { mUserManager = context.getSystemService(UserManager.class); mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false); + + injectRegisterUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE + | ActivityManager.UID_OBSERVER_GONE); } void logDurationStat(int statId, long start) { @@ -306,6 +332,59 @@ public class ShortcutService extends IShortcutService.Stub { } } + public long getLocaleChangeSequenceNumber() { + return mLocaleChangeSequenceNumber.get(); + } + + final private IUidObserver mUidObserver = new IUidObserver.Stub() { + @Override public void onUidStateChanged(int uid, int procState) throws RemoteException { + handleOnUidStateChanged(uid, procState); + } + + @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 onUidIdle(int uid) throws RemoteException { + } + }; + + void handleOnUidStateChanged(int uid, int procState) { + if (DEBUG_PROCSTATE) { + Slog.d(TAG, "onUidStateChanged: uid=" + uid + " state=" + procState); + } + synchronized (mLock) { + mUidState.put(uid, procState); + + // We need to keep track of last time an app comes to foreground. + // See ShortcutPackage.getApiCallCount() for how it's used. + // It doesn't have to be persisted, but it needs to be the elapsed time. + if (isProcessStateForeground(procState)) { + mUidLastForegroundElapsedTime.put(uid, injectElapsedRealtime()); + } + } + } + + private boolean isProcessStateForeground(int processState) { + return processState <= PROCESS_STATE_FOREGROUND_THRESHOLD; + } + + boolean isUidForegroundLocked(int uid) { + if (uid == Process.SYSTEM_UID) { + // IUidObserver doesn't report the state of SYSTEM, but it always has bound services, + // so it's foreground anyway. + return true; + } + return isProcessStateForeground(mUidState.get(uid, ActivityManager.MAX_PROCESS_STATE)); + } + + long getUidLastForegroundElapsedTimeLocked(int uid) { + return mUidLastForegroundElapsedTime.get(uid); + } + /** * System service lifecycle. */ @@ -596,6 +675,8 @@ public class ShortcutService extends IShortcutService.Stub { // Body. writeTagValue(out, TAG_LAST_RESET_TIME, mRawLastResetTime); + writeTagValue(out, TAG_LOCALE_CHANGE_SEQUENCE_NUMBER, + mLocaleChangeSequenceNumber.get()); // Epilogue. out.endTag(null, TAG_ROOT); @@ -640,6 +721,9 @@ public class ShortcutService extends IShortcutService.Stub { case TAG_LAST_RESET_TIME: mRawLastResetTime = parseLongAttribute(parser, ATTR_VALUE); break; + case TAG_LOCALE_CHANGE_SEQUENCE_NUMBER: + mLocaleChangeSequenceNumber.set(parseLongAttribute(parser, ATTR_VALUE)); + break; default: Slog.e(TAG, "Invalid tag: " + tag); break; @@ -993,20 +1077,6 @@ public class ShortcutService extends IShortcutService.Stub { bitmap = icon.getBitmap(); // Don't recycle in this case. break; } - case Icon.TYPE_URI: { - final Uri uri = ContentProvider.maybeAddUserId(icon.getUri(), userId); - - try (InputStream is = mContext.getContentResolver().openInputStream(uri)) { - - bitmapToRecycle = BitmapFactory.decodeStream(is); - bitmap = bitmapToRecycle; - - } catch (IOException e) { - Slog.e(TAG, "Unable to load icon from " + uri); - return; - } - break; - } default: // This shouldn't happen because we've already validated the icon, but // just in case. @@ -1122,6 +1192,24 @@ public class ShortcutService extends IShortcutService.Stub { Preconditions.checkState(isCallerSystem(), "Caller must be system"); } + private void enforceResetThrottlingPermission() { + if (isCallerSystem()) { + return; + } + injectEnforceCallingPermission( + android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null); + } + + /** + * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse + * mockito. So instead we extracted it here and override it in the tests. + */ + @VisibleForTesting + void injectEnforceCallingPermission( + @NonNull String permission, @Nullable String message) { + mContext.enforceCallingPermission(permission, message); + } + private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { Preconditions.checkStringNotEmpty(packageName, "packageName"); @@ -1481,6 +1569,23 @@ public class ShortcutService extends IShortcutService.Stub { Slog.i(TAG, "ShortcutManager: throttling counter reset for all users"); } + void resetPackageThrottling(String packageName, int userId) { + synchronized (mLock) { + getPackageShortcutsLocked(packageName, userId) + .resetRateLimitingForCommandLineNoSaving(); + saveUserLocked(userId); + } + } + + @Override + public void onApplicationActive(String packageName, int userId) { + if (DEBUG) { + Slog.d(TAG, "onApplicationActive: package=" + packageName + " userid=" + userId); + } + enforceResetThrottlingPermission(); + resetPackageThrottling(packageName, userId); + } + // We override this method in unit tests to do a simpler check. boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) { return hasShortcutHostPermissionInner(callingPackage, userId); @@ -1593,15 +1698,11 @@ public class ShortcutService extends IShortcutService.Stub { user.removeLauncher(packageUserId, packageName); // Then remove pinned shortcuts from all launchers. - final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = user.getAllLaunchers(); - for (int i = launchers.size() - 1; i >= 0; i--) { - launchers.valueAt(i).cleanUpPackage(packageName, packageUserId); - } - // Now there may be orphan shortcuts because we removed pinned shortucts at the previous + user.forAllLaunchers(l -> l.cleanUpPackage(packageName, packageUserId)); + + // Now there may be orphan shortcuts because we removed pinned shortcuts at the previous // step. Remove them too. - for (int i = user.getAllPackages().size() - 1; i >= 0; i--) { - user.getAllPackages().valueAt(i).refreshPinnedFlags(this); - } + user.forAllPackages(p -> p.refreshPinnedFlags(this)); scheduleSaveUser(owningUserId); @@ -1644,13 +1745,12 @@ public class ShortcutService extends IShortcutService.Stub { callingPackage, packageName, shortcutIds, changedSince, componentName, queryFlags, userId, ret, cloneFlag); } else { - final ArrayMap<String, ShortcutPackage> packages = - getUserShortcutsLocked(userId).getAllPackages(); - for (int i = packages.size() - 1; i >= 0; i--) { + final List<String> shortcutIdsF = shortcutIds; + getUserShortcutsLocked(userId).forAllPackages(p -> { getShortcutsInnerLocked(launcherUserId, - callingPackage, packages.keyAt(i), shortcutIds, changedSince, + callingPackage, p.getPackageName(), shortcutIdsF, changedSince, componentName, queryFlags, userId, ret, cloneFlag); - } + }); } } return ret; @@ -1819,6 +1919,29 @@ public class ShortcutService extends IShortcutService.Stub { @NonNull String callingPackage) { return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId); } + + /** + * Called by AM when the system locale changes *within the AM lock. ABSOLUTELY do not take + * any locks in this method. + */ + @Override + public void onSystemLocaleChangedNoLock() { + // DO NOT HOLD ANY LOCKS HERE. + + // We want to reset throttling for all packages for all users. But we can't just do so + // here because: + // - We can't load/save users that are locked. + // - Even for loaded users, resetting the counters would require us to hold mLock. + // + // So we use a "pull" model instead. In here, we just increment the "locale change + // sequence number". Each ShortcutUser has the "last known locale change sequence". + // + // This allows ShortcutUser's to detect the system locale change, so they can reset + // counters. + + mLocaleChangeSequenceNumber.incrementAndGet(); + postToHandler(() -> scheduleSaveBaseState()); + } } /** @@ -2087,11 +2210,11 @@ public class ShortcutService extends IShortcutService.Stub { + android.Manifest.permission.DUMP); return; } - dumpInner(pw); + dumpInner(pw, args); } @VisibleForTesting - void dumpInner(PrintWriter pw) { + void dumpInner(PrintWriter pw, String[] args) { synchronized (mLock) { final long now = injectCurrentTimeMillis(); pw.print("Now: ["); @@ -2115,6 +2238,9 @@ public class ShortcutService extends IShortcutService.Stub { pw.print(next); pw.print("] "); pw.print(formatTime(next)); + + pw.print(" Locale change seq#: "); + pw.print(mLocaleChangeSequenceNumber.get()); pw.println(); pw.print(" Config:"); @@ -2149,6 +2275,24 @@ public class ShortcutService extends IShortcutService.Stub { pw.println(); mUsers.valueAt(i).dump(this, pw, " "); } + + pw.println(); + pw.println(" UID state:"); + + for (int i = 0; i < mUidState.size(); i++) { + final int uid = mUidState.keyAt(i); + final int state = mUidState.valueAt(i); + pw.print(" UID="); + pw.print(uid); + pw.print(" state="); + pw.print(state); + if (isProcessStateForeground(state)) { + pw.print(" [FG]"); + } + pw.print(" last FG="); + pw.print(mUidLastForegroundElapsedTime.get(uid)); + pw.println(); + } } } @@ -2316,10 +2460,7 @@ public class ShortcutService extends IShortcutService.Stub { Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName); - synchronized (mLock) { - getPackageShortcutsLocked(packageName, mUserId).resetRateLimitingForCommandLine(); - saveUserLocked(mUserId); - } + resetPackageThrottling(packageName, mUserId); } private void handleOverrideConfig() throws CommandException { @@ -2404,6 +2545,11 @@ public class ShortcutService extends IShortcutService.Stub { return System.currentTimeMillis(); } + @VisibleForTesting + long injectElapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + // Injection point. @VisibleForTesting int injectBinderCallingUid() { @@ -2451,6 +2597,14 @@ public class ShortcutService extends IShortcutService.Stub { } @VisibleForTesting + void injectRegisterUidObserver(IUidObserver observer, int which) { + try { + ActivityManagerNative.getDefault().registerUidObserver(observer, which); + } catch (RemoteException shouldntHappen) { + } + } + + @VisibleForTesting PackageManagerInternal injectPackageManagerInternal() { return mPackageManagerInternal; } @@ -2500,7 +2654,7 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutUser user = mUsers.get(userId); if (user == null) return null; - final ShortcutPackage pkg = user.getAllPackages().get(packageName); + final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName); if (pkg == null) return null; return pkg.findShortcutById(shortcutId); diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 3d2e2ec49138..7d19a78dda6b 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -21,7 +21,9 @@ import android.content.ComponentName; import android.text.format.Formatter; import android.util.ArrayMap; import android.util.Slog; +import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import libcore.util.Objects; @@ -45,6 +47,7 @@ class ShortcutUser { private static final String TAG_LAUNCHER = "launcher"; private static final String ATTR_VALUE = "value"; + private static final String ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER = "locale-seq-no"; static final class PackageWithUser { final int userId; @@ -89,10 +92,15 @@ 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. */ private ComponentName mLauncherComponent; + private long mKnownLocaleChangeSequenceNumber; + public ShortcutUser(int userId) { mUserId = userId; } @@ -101,7 +109,10 @@ class ShortcutUser { return mUserId; } - public ArrayMap<String, ShortcutPackage> getAllPackages() { + // We don't expose this directly to non-test code because only ShortcutUser should add to/ + // remove from it. + @VisibleForTesting + ArrayMap<String, ShortcutPackage> getAllPackagesForTest() { return mPackages; } @@ -113,7 +124,10 @@ class ShortcutUser { return removed; } - public ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchers() { + // We don't expose this directly to non-test code because only ShortcutUser should add to/ + // remove from it. + @VisibleForTesting + ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() { return mLaunchers; } @@ -130,7 +144,7 @@ class ShortcutUser { public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) { ShortcutPackage ret = mPackages.get(packageName); if (ret == null) { - ret = new ShortcutPackage(mUserId, packageName); + ret = new ShortcutPackage(s, this, mUserId, packageName); mPackages.put(packageName, ret); } else { ret.attemptToRestoreIfNeededAndSave(s); @@ -143,7 +157,7 @@ class ShortcutUser { final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); ShortcutLauncher ret = mLaunchers.get(key); if (ret == null) { - ret = new ShortcutLauncher(mUserId, packageName, launcherUserId); + ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId); mLaunchers.put(key, ret); } else { ret.attemptToRestoreIfNeededAndSave(s); @@ -151,21 +165,25 @@ class ShortcutUser { return ret; } - public void forAllPackageItems(Consumer<ShortcutPackageItem> callback) { - { - final int size = mLaunchers.size(); - for (int i = 0; i < size; i++) { - callback.accept(mLaunchers.valueAt(i)); - } + public void forAllPackages(Consumer<? super ShortcutPackage> callback) { + final int size = mPackages.size(); + for (int i = 0; i < size; i++) { + callback.accept(mPackages.valueAt(i)); } - { - final int size = mPackages.size(); - for (int i = 0; i < size; i++) { - callback.accept(mPackages.valueAt(i)); - } + } + + public void forAllLaunchers(Consumer<? super ShortcutLauncher> callback) { + final int size = mLaunchers.size(); + for (int i = 0; i < size; i++) { + callback.accept(mLaunchers.valueAt(i)); } } + public void forAllPackageItems(Consumer<? super ShortcutPackageItem> callback) { + forAllLaunchers(callback); + forAllPackages(callback); + } + public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId, Consumer<ShortcutPackageItem> callback) { forAllPackageItems(spi -> { @@ -177,6 +195,24 @@ class ShortcutUser { } /** + * Reset all throttling counters for all packages, if there has been a system locale change. + */ + public void resetThrottlingIfNeeded(ShortcutService s) { + final long currentNo = s.getLocaleChangeSequenceNumber(); + if (mKnownLocaleChangeSequenceNumber < currentNo) { + if (ShortcutService.DEBUG) { + Slog.d(TAG, "LocaleChange detected for user " + mUserId); + } + + mKnownLocaleChangeSequenceNumber = currentNo; + + forAllPackages(p -> p.resetRateLimiting(s)); + + s.scheduleSaveUser(mUserId); + } + } + + /** * Called when a package is updated. */ public void handlePackageUpdated(ShortcutService s, @NonNull String packageName, @@ -198,6 +234,9 @@ class ShortcutUser { throws IOException, XmlPullParserException { out.startTag(null, TAG_ROOT); + ShortcutService.writeAttr(out, ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER, + mKnownLocaleChangeSequenceNumber); + ShortcutService.writeTagValue(out, TAG_LAUNCHER, mLauncherComponent); @@ -235,6 +274,9 @@ class ShortcutUser { boolean fromBackup) throws IOException, XmlPullParserException { final ShortcutUser ret = new ShortcutUser(userId); + ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser, + ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER); + final int outerDepth = parser.getDepth(); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT @@ -254,7 +296,7 @@ class ShortcutUser { } case ShortcutPackage.TAG_ROOT: { final ShortcutPackage shortcuts = ShortcutPackage.loadFromXml( - s, parser, userId, fromBackup); + s, ret, parser, fromBackup); // Don't use addShortcut(), we don't need to save the icon. ret.mPackages.put(shortcuts.getPackageName(), shortcuts); @@ -262,7 +304,8 @@ class ShortcutUser { } case ShortcutLauncher.TAG_ROOT: { - ret.addLauncher(ShortcutLauncher.loadFromXml(parser, userId, fromBackup)); + ret.addLauncher( + ShortcutLauncher.loadFromXml(parser, ret, userId, fromBackup)); continue; } } @@ -294,6 +337,8 @@ class ShortcutUser { pw.print(prefix); pw.print("User: "); pw.print(mUserId); + pw.print(" Known locale seq#: "); + pw.print(mKnownLocaleChangeSequenceNumber); pw.println(); prefix += prefix + " "; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index 13518b555c76..ced7cf03e938 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -56,11 +56,14 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest.permission; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.Activity; +import android.app.ActivityManager; +import android.app.IUidObserver; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -94,6 +97,7 @@ import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.test.InstrumentationTestCase; +import android.test.MoreAsserts; import android.test.mock.MockContext; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; @@ -225,6 +229,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { /** ShortcutService with injection override methods. */ private final class ShortcutServiceTestable extends ShortcutService { final ServiceContext mContext; + IUidObserver mUidObserver; public ShortcutServiceTestable(ServiceContext context, Looper looper) { super(context, looper); @@ -265,6 +270,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { } @Override + long injectElapsedRealtime() { + // TODO This should be kept separately from mInjectedCurrentTimeLillis, since + // this should increase even if we rewind mInjectedCurrentTimeLillis in some tests. + return mInjectedCurrentTimeLillis - START_TIME; + } + + @Override int injectBinderCallingUid() { return mInjectedCallingUid; } @@ -295,6 +307,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { } @Override + void injectRegisterUidObserver(IUidObserver observer, int which) { + mUidObserver = observer; + } + + @Override PackageManagerInternal injectPackageManagerInternal() { return mMockPackageManagerInternal; } @@ -324,6 +341,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase { } @Override + void injectEnforceCallingPermission(String permission, String message) { + if (!mCallerPermissions.contains(permission)) { + throw new SecurityException("Missing permission: " + permission); + } + } + + @Override void wtf(String message, Exception e) { // During tests, WTF is fatal. fail(message + " exception: " + e); @@ -493,6 +517,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private static final ShortcutQuery QUERY_ALL = new ShortcutQuery(); + private final ArrayList<String> mCallerPermissions = new ArrayList<>(); + static { QUERY_ALL.setQueryFlags( ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_PINNED); @@ -561,6 +587,11 @@ public class ShortcutManagerTest extends InstrumentationTestCase { initService(); setCaller(CALLING_PACKAGE_1); + + // In order to complicate the situation, we set mLocaleChangeSequenceNumber to 1 by + // calling this. Running test with mLocaleChangeSequenceNumber == 0 might make us miss + // some edge cases. + mInternal.onSystemLocaleChangedNoLock(); } private static UserInfo withProfileGroupId(UserInfo in, int groupId) { @@ -761,7 +792,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintWriter pw = new PrintWriter(out); - mService.dumpInner(pw); + mService.dumpInner(pw, null); pw.close(); Log.e(TAG, "Dumping ShortcutService: " + message); @@ -3546,17 +3577,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // Check the registered packages. dumpsysOnLogcat(); assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - hashSet(user0.getAllPackages().keySet())); + hashSet(user0.getAllPackagesForTest().keySet())); assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - hashSet(user10.getAllPackages().keySet())); + hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - hashSet(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - hashSet(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_1", "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3578,17 +3609,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { // No changes. assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - hashSet(user0.getAllPackages().keySet())); + hashSet(user0.getAllPackagesForTest().keySet())); assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - hashSet(user10.getAllPackages().keySet())); + hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - hashSet(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - hashSet(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_1", "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3609,17 +3640,17 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_0, USER_0); assertEquals(set(CALLING_PACKAGE_2), - hashSet(user0.getAllPackages().keySet())); + hashSet(user0.getAllPackagesForTest().keySet())); assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - hashSet(user10.getAllPackages().keySet())); + hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - hashSet(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_10, LAUNCHER_1), PackageWithUser.of(USER_10, LAUNCHER_2)), - hashSet(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3640,16 +3671,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.cleanUpPackageLocked(LAUNCHER_1, USER_10, USER_10); assertEquals(set(CALLING_PACKAGE_2), - hashSet(user0.getAllPackages().keySet())); + hashSet(user0.getAllPackagesForTest().keySet())); assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), - hashSet(user10.getAllPackages().keySet())); + hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - hashSet(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_10, LAUNCHER_2)), - hashSet(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3668,16 +3699,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.cleanUpPackageLocked(CALLING_PACKAGE_2, USER_10, USER_10); assertEquals(set(CALLING_PACKAGE_2), - hashSet(user0.getAllPackages().keySet())); + hashSet(user0.getAllPackagesForTest().keySet())); assertEquals(set(CALLING_PACKAGE_1), - hashSet(user10.getAllPackages().keySet())); + hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - hashSet(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_10, LAUNCHER_2)), - hashSet(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3696,16 +3727,16 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.cleanUpPackageLocked(LAUNCHER_2, USER_10, USER_10); assertEquals(set(CALLING_PACKAGE_2), - hashSet(user0.getAllPackages().keySet())); + hashSet(user0.getAllPackagesForTest().keySet())); assertEquals(set(CALLING_PACKAGE_1), - hashSet(user10.getAllPackages().keySet())); + hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - hashSet(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( set(), - hashSet(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -3724,15 +3755,15 @@ public class ShortcutManagerTest extends InstrumentationTestCase { mService.cleanUpPackageLocked(CALLING_PACKAGE_1, USER_10, USER_10); assertEquals(set(CALLING_PACKAGE_2), - hashSet(user0.getAllPackages().keySet())); + hashSet(user0.getAllPackagesForTest().keySet())); assertEquals(set(), - hashSet(user10.getAllPackages().keySet())); + hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( set(PackageWithUser.of(USER_0, LAUNCHER_1), PackageWithUser.of(USER_0, LAUNCHER_2)), - hashSet(user0.getAllLaunchers().keySet())); + hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals(set(), - hashSet(user10.getAllLaunchers().keySet())); + hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_2, USER_0), @@ -4654,19 +4685,19 @@ public class ShortcutManagerTest extends InstrumentationTestCase { private void checkBackupAndRestore_success() { // Make sure non-system user is not restored. final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0); - assertEquals(0, userP0.getAllPackages().size()); - assertEquals(0, userP0.getAllLaunchers().size()); + assertEquals(0, userP0.getAllPackagesForTest().size()); + assertEquals(0, userP0.getAllLaunchersForTest().size()); // Make sure only "allowBackup" apps are restored, and are shadow. final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0); - assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_1)); - assertExistsAndShadow(user0.getAllPackages().get(CALLING_PACKAGE_2)); - assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_1))); - assertExistsAndShadow(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_2))); + assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_1)); + assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2)); + assertExistsAndShadow(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_1))); + assertExistsAndShadow(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_2))); - assertNull(user0.getAllPackages().get(CALLING_PACKAGE_3)); - assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_0, LAUNCHER_3))); - assertNull(user0.getAllLaunchers().get(PackageWithUser.of(USER_P0, LAUNCHER_1))); + assertNull(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3)); + assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3))); + assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1))); installPackage(USER_0, CALLING_PACKAGE_1); runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { @@ -5285,6 +5316,361 @@ public class ShortcutManagerTest extends InstrumentationTestCase { }); } + public void testThrottling_localeChanges() { + prepareCrossProfileDataSet(); + + dumpsysOnLogcat("Before save & load"); + + mService.saveDirtyInfo(); + initService(); + + final long origSequenceNumber = mService.getLocaleChangeSequenceNumber(); + + mInternal.onSystemLocaleChangedNoLock(); + + assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber()); + + // Note at this point only user-0 is loaded, and the counters are reset for this user, + // but it will work for other users too, because we persist when + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + + mService.saveDirtyInfo(); + initService(); + + // Make sure the counter is persisted. + assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber()); + } + + public void testThrottling_foreground() throws Exception { + prepareCrossProfileDataSet(); + + dumpsysOnLogcat("Before save & load"); + + mService.saveDirtyInfo(); + initService(); + + // We need to update the current time from time to time, since some of the internal checks + // rely on the time being correctly incremented. + mInjectedCurrentTimeLillis++; + + // First, all packages have less than 3 (== initial value) remaining calls. + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + + mInjectedCurrentTimeLillis++; + + // State changed, but not foreground, so no resetting. + mService.mUidObserver.onUidStateChanged( + CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + + mInjectedCurrentTimeLillis++; + + // State changed, package1 foreground, reset. + mService.mUidObserver.onUidStateChanged( + CALLING_UID_1, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + mService.mUidObserver.onUidStateChanged( + CALLING_UID_1, ActivityManager.PROCESS_STATE_TOP_SLEEPING); + + mInjectedCurrentTimeLillis++; + + // Different app comes to foreground briefly, and goes back to background. + // Now, make sure package 2's counter is reset, even in this case. + mService.mUidObserver.onUidStateChanged( + CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + mService.mUidObserver.onUidStateChanged( + CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + + mInjectedCurrentTimeLillis++; + + // Do the same thing one more time. This would catch the bug with mixuing up + // the current time and the elapsed time. + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + mManager.updateShortcuts(list(makeShortcut("s"))); + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + + mService.mUidObserver.onUidStateChanged( + CALLING_UID_2, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + mService.mUidObserver.onUidStateChanged( + CALLING_UID_2, ActivityManager.PROCESS_STATE_TOP_SLEEPING); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + + mInjectedCurrentTimeLillis++; + + // Package 1 on user-10 comes to foreground. + // Now, also try calling some APIs and make sure foreground apps don't get throttled. + mService.mUidObserver.onUidStateChanged( + UserHandle.getUid(USER_10, CALLING_UID_1), + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + + assertEquals(0, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + + assertEquals(0, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + + assertEquals(0, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + + assertEquals(0, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + + assertEquals(0, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + mManager.setDynamicShortcuts(list(makeShortcut("s"))); + + assertEquals(3, mManager.getRemainingCallCount()); // Still 3! + }); + } + + + public void testThrottling_resetByInternalCall() throws Exception { + prepareCrossProfileDataSet(); + + dumpsysOnLogcat("Before save & load"); + + mService.saveDirtyInfo(); + initService(); + + // First, all packages have less than 3 (== initial value) remaining calls. + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + + // Simulate a call from sys UI. + mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING); + mService.onApplicationActive(CALLING_PACKAGE_1, USER_0); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + + mService.onApplicationActive(CALLING_PACKAGE_3, USER_0); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + + mService.onApplicationActive(CALLING_PACKAGE_1, USER_10); + + runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_4, USER_0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> { + MoreAsserts.assertNotEqual(3, mManager.getRemainingCallCount()); + }); + runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { + assertEquals(3, mManager.getRemainingCallCount()); + }); + } + + public void testOnApplicationActive_permission() { + assertExpectException(SecurityException.class, "Missing permission", () -> + mService.onApplicationActive(CALLING_PACKAGE_1, USER_0)); + + // Has permission, now it should pass. + mCallerPermissions.add(permission.RESET_SHORTCUT_MANAGER_THROTTLING); + mService.onApplicationActive(CALLING_PACKAGE_1, USER_0); + } + // ShortcutInfo tests public void testShortcutInfoMissingMandatoryFields() { @@ -5324,7 +5710,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { si = new ShortcutInfo.Builder(getTestContext()) .setId("id") .setActivityComponent(new ComponentName("a", "b")) - .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setIcon(Icon.createWithResource(mClientContext, 123)) .setTitle("title") .setText("text") .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) @@ -5341,7 +5727,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(getTestContext().getPackageName(), si.getPackageName()); assertEquals("id", si.getId()); assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); - assertEquals("content://a.b.c/", si.getIcon().getUriString()); + assertEquals(123, si.getIcon().getResId()); assertEquals("title", si.getTitle()); assertEquals("text", si.getText()); assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); @@ -5363,7 +5749,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext) .setId("id") .setActivityComponent(new ComponentName("a", "b")) - .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setIcon(Icon.createWithResource(mClientContext, 123)) .setTitle("title") .setText("text") .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) @@ -5382,7 +5768,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { assertEquals(mClientContext.getPackageName(), si.getPackageName()); assertEquals("id", si.getId()); assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); - assertEquals("content://a.b.c/", si.getIcon().getUriString()); + assertEquals(123, si.getIcon().getResId()); assertEquals("title", si.getTitle()); assertEquals("text", si.getText()); assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); @@ -5498,7 +5884,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { ShortcutInfo sorig = new ShortcutInfo.Builder(getTestContext()) .setId("id") .setActivityComponent(new ComponentName("a", "b")) - .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setIcon(Icon.createWithResource(mClientContext, 123)) .setTitle("title") .setText("text") .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) @@ -5520,9 +5906,9 @@ public class ShortcutManagerTest extends InstrumentationTestCase { si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") - .setIcon(Icon.createWithContentUri("content://x.y.z/")).build()); + .setIcon(Icon.createWithResource(mClientContext, 456)).build()); assertEquals("text", si.getText()); - assertEquals("content://x.y.z/", si.getIcon().getUriString()); + assertEquals(456, si.getIcon().getResId()); si = sorig.clone(/* flags=*/ 0); si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getTestContext()).setId("id") |