Refactoring ShortcutManager + bug fixes.

- Don't pass the ShortcutService instance as an argument.  This tiny
optimization is no longer meaningful now that PackageShortcut and
PackageLauncher have reference to ShortcutUser.

- Rename mLauncherComponent to mDefaultLauncherComponent for clarity.

- Don't instantiate ShortcutPackage instances when not needed.

- Don't allow intents with a null action.

- Also improve javadoc.

Bug 28592642
Bug 28474517
Bug 28557169

Change-Id: I8790d3494bf3b92c143c02824b0ed0e514504baa
This commit is contained in:
Makoto Onuki
2016-05-04 15:24:50 -07:00
parent de224ba451
commit c51b2876ec
9 changed files with 242 additions and 161 deletions

View File

@@ -439,6 +439,7 @@ 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.");
return this;
}

View File

@@ -18,7 +18,6 @@ package android.content.pm;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -80,8 +79,18 @@ import java.util.List;
*
* <h3>Backup and Restore</h3>
*
* Shortcuts will be backed up and restored across devices. This means all information, including
* IDs, must be meaningful on a different device.
* Pinned shortcuts will be backed up and restored across devices. This means all information
* within shortcuts, including IDs, must be meaningful on different devices.
*
* <p>Note that:
* <ul>
* <li>Dynamic shortcuts will not be backed up or restored.
* <li>Icons of pinned shortcuts will <b>not</b> be backed up for performance reasons, unless
* they refer to resources. Instead, launcher applications are supposed to back up and restore
* icons of pinned shortcuts by themselves, and thus from the user's point of view, pinned
* shortcuts will look to have icons restored.
* </ul>
*
*
* <h3>APIs for launcher</h3>
*
@@ -147,8 +156,8 @@ public class ShortcutManager {
}
/**
* Publish a single dynamic shortcut. If there's already dynamic or pinned shortcuts with
* the same ID, they will all be updated.
* Publish list of dynamic shortcuts. If there's already dynamic or pinned shortcuts with
* the same IDs, they will all be updated.
*
* <p>This API will be rate-limited.
*
@@ -167,7 +176,7 @@ public class ShortcutManager {
}
/**
* Delete a single dynamic shortcut by ID.
* Delete dynamic shortcuts by ID.
*/
public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) {
try {

View File

@@ -80,26 +80,31 @@ class ShortcutLauncher extends ShortcutPackageItem {
* Called when the new package can't receive the backup, due to signature or version mismatch.
*/
@Override
protected void onRestoreBlocked(ShortcutService s) {
protected void onRestoreBlocked() {
final ArrayList<PackageWithUser> pinnedPackages =
new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
final PackageWithUser pu = pinnedPackages.get(i);
s.getPackageShortcutsLocked(pu.packageName, pu.userId)
.refreshPinnedFlags(s);
final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(pu.packageName);
if (p != null) {
p.refreshPinnedFlags();
}
}
}
@Override
protected void onRestored(ShortcutService s) {
protected void onRestored() {
// Nothing to do.
}
public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
public void pinShortcuts(@UserIdInt int packageUserId,
@NonNull String packageName, @NonNull List<String> ids) {
final ShortcutPackage packageShortcuts =
s.getPackageShortcutsLocked(packageName, packageUserId);
mShortcutUser.getPackageShortcutsIfExists(packageName);
if (packageShortcuts == null) {
return; // No need to instantiate.
}
final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
@@ -126,7 +131,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
}
mPinnedShortcuts.put(pu, newSet);
}
packageShortcuts.refreshPinnedFlags(s);
packageShortcuts.refreshPinnedFlags();
}
/**
@@ -240,7 +245,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
return ret;
}
public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println();
pw.print(prefix);
@@ -252,7 +257,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
pw.print(getOwnerUserId());
pw.println();
getPackageInfo().dump(s, pw, prefix + " ");
getPackageInfo().dump(pw, prefix + " ");
pw.println();
final int size = mPinnedShortcuts.size();

View File

@@ -98,17 +98,16 @@ class ShortcutPackage extends ShortcutPackageItem {
private long mLastKnownForegroundElapsedTime;
private ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
private ShortcutPackage(ShortcutUser shortcutUser,
int packageUserId, String packageName, ShortcutPackageInfo spi) {
super(shortcutUser, packageUserId, packageName,
spi != null ? spi : ShortcutPackageInfo.newEmpty());
mPackageUid = s.injectGetPackageUid(packageName, packageUserId);
mPackageUid = shortcutUser.mService.injectGetPackageUid(packageName, packageUserId);
}
public ShortcutPackage(ShortcutService s, ShortcutUser shortcutUser,
int packageUserId, String packageName) {
this(s, shortcutUser, packageUserId, packageName, null);
public ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName) {
this(shortcutUser, packageUserId, packageName, null);
}
@Override
@@ -126,10 +125,12 @@ class ShortcutPackage extends ShortcutPackageItem {
* exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
* we do some initialization for the package.
*/
private void onShortcutPublish(ShortcutService s) {
private void onShortcutPublish() {
// Make sure we have the version code for the app. We need the version code in
// handlePackageUpdated().
if (getPackageInfo().getVersionCode() < 0) {
final ShortcutService s = mShortcutUser.mService;
final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId());
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
@@ -143,16 +144,16 @@ class ShortcutPackage extends ShortcutPackageItem {
}
@Override
protected void onRestoreBlocked(ShortcutService s) {
protected void onRestoreBlocked() {
// Can't restore due to version/signature mismatch. Remove all shortcuts.
mShortcuts.clear();
}
@Override
protected void onRestored(ShortcutService s) {
protected void onRestored() {
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
refreshPinnedFlags(s);
refreshPinnedFlags();
}
/**
@@ -163,19 +164,18 @@ class ShortcutPackage extends ShortcutPackageItem {
return mShortcuts.get(id);
}
private ShortcutInfo deleteShortcut(@NonNull ShortcutService s,
@NonNull String id) {
private ShortcutInfo deleteShortcut(@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) {
s.removeIcon(getPackageUserId(), shortcut);
mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED);
}
return shortcut;
}
void addShortcut(@NonNull ShortcutService s, @NonNull ShortcutInfo newShortcut) {
deleteShortcut(s, newShortcut.getId());
s.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
void addShortcut(@NonNull ShortcutInfo newShortcut) {
deleteShortcut(newShortcut.getId());
mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
mShortcuts.put(newShortcut.getId(), newShortcut);
}
@@ -184,10 +184,9 @@ class ShortcutPackage extends ShortcutPackageItem {
*
* It checks the max number of dynamic shortcuts.
*/
public void addDynamicShortcut(@NonNull ShortcutService s,
@NonNull ShortcutInfo newShortcut) {
public void addDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
onShortcutPublish(s);
onShortcutPublish();
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
@@ -209,21 +208,21 @@ class ShortcutPackage extends ShortcutPackageItem {
}
// Make sure there's still room.
s.enforceMaxDynamicShortcuts(newDynamicCount);
mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
// Okay, make it dynamic and add.
if (wasPinned) {
newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
}
addShortcut(s, newShortcut);
addShortcut(newShortcut);
mDynamicShortcutCount = newDynamicCount;
}
/**
* Remove all shortcuts that aren't pinned nor dynamic.
*/
private void removeOrphans(@NonNull ShortcutService s) {
private void removeOrphans() {
ArrayList<String> removeList = null; // Lazily initialize.
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
@@ -238,7 +237,7 @@ class ShortcutPackage extends ShortcutPackageItem {
}
if (removeList != null) {
for (int i = removeList.size() - 1; i >= 0; i--) {
deleteShortcut(s, removeList.get(i));
deleteShortcut(removeList.get(i));
}
}
}
@@ -246,18 +245,18 @@ class ShortcutPackage extends ShortcutPackageItem {
/**
* Remove all dynamic shortcuts.
*/
public void deleteAllDynamicShortcuts(@NonNull ShortcutService s) {
public void deleteAllDynamicShortcuts() {
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC);
}
removeOrphans(s);
removeOrphans();
mDynamicShortcutCount = 0;
}
/**
* Remove a dynamic shortcut by ID.
*/
public void deleteDynamicWithId(@NonNull ShortcutService s, @NonNull String shortcutId) {
public void deleteDynamicWithId(@NonNull String shortcutId) {
final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
if (oldShortcut == null) {
@@ -269,7 +268,7 @@ class ShortcutPackage extends ShortcutPackageItem {
if (oldShortcut.isPinned()) {
oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
} else {
deleteShortcut(s, shortcutId);
deleteShortcut(shortcutId);
}
}
@@ -279,14 +278,15 @@ class ShortcutPackage extends ShortcutPackageItem {
*
* <p>Then remove all shortcuts that are not dynamic and no longer pinned either.
*/
public void refreshPinnedFlags(@NonNull ShortcutService s) {
public void refreshPinnedFlags() {
// First, un-pin all shortcuts
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_PINNED);
}
// Then, for the pinned set for each launcher, set the pin flag one by one.
s.getUserShortcutsLocked(getPackageUserId()).forAllLaunchers(launcherShortcuts -> {
mShortcutUser.mService.getUserShortcutsLocked(getPackageUserId())
.forAllLaunchers(launcherShortcuts -> {
final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
getPackageName(), getPackageUserId());
@@ -308,7 +308,7 @@ class ShortcutPackage extends ShortcutPackageItem {
});
// Lastly, remove the ones that are no longer pinned nor dynamic.
removeOrphans(s);
removeOrphans();
}
/**
@@ -317,8 +317,10 @@ class ShortcutPackage extends ShortcutPackageItem {
* <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);
public int getApiCallCount() {
mShortcutUser.resetThrottlingIfNeeded();
final ShortcutService s = mShortcutUser.mService;
// Reset the counter if:
// - the package is in foreground now.
@@ -328,7 +330,7 @@ class ShortcutPackage extends ShortcutPackageItem {
|| mLastKnownForegroundElapsedTime
< s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) {
mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
resetRateLimiting(s);
resetRateLimiting();
}
// Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount,
@@ -349,8 +351,8 @@ class ShortcutPackage extends ShortcutPackageItem {
// If not reset yet, then reset.
if (mLastResetTime < last) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("My last reset=%d, now=%d, last=%d: resetting",
mLastResetTime, now, last));
Slog.d(TAG, String.format("%s: last reset=%d, now=%d, last=%d: resetting",
getPackageName(), mLastResetTime, now, last));
}
mApiCallCount = 0;
mLastResetTime = last;
@@ -365,8 +367,10 @@ class ShortcutPackage extends ShortcutPackageItem {
* <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) {
public boolean tryApiCall() {
final ShortcutService s = mShortcutUser.mService;
if (getApiCallCount() >= s.mMaxUpdatesPerInterval) {
return false;
}
mApiCallCount++;
@@ -374,13 +378,13 @@ class ShortcutPackage extends ShortcutPackageItem {
return true;
}
public void resetRateLimiting(@NonNull ShortcutService s) {
public void resetRateLimiting() {
if (ShortcutService.DEBUG) {
Slog.d(TAG, "resetRateLimiting: " + getPackageName());
}
if (mApiCallCount > 0) {
mApiCallCount = 0;
s.scheduleSaveUser(getOwnerUserId());
mShortcutUser.mService.scheduleSaveUser(getOwnerUserId());
}
}
@@ -392,9 +396,9 @@ class ShortcutPackage extends ShortcutPackageItem {
/**
* Find all shortcuts that match {@code query}.
*/
public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
public void findAll(@NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
findAll(s, result, query, cloneFlag, null, 0);
findAll(result, query, cloneFlag, null, 0);
}
/**
@@ -404,7 +408,7 @@ class ShortcutPackage extends ShortcutPackageItem {
* by the calling launcher will not be included in the result, and also "isPinned" will be
* adjusted for the caller too.
*/
public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
public void findAll(@NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag,
@Nullable String callingLauncher, int launcherUserId) {
if (getPackageInfo().isShadow()) {
@@ -412,6 +416,8 @@ class ShortcutPackage extends ShortcutPackageItem {
return;
}
final ShortcutService s = mShortcutUser.mService;
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
: s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
@@ -479,7 +485,7 @@ class ShortcutPackage extends ShortcutPackageItem {
* Called when the package is updated. If there are shortcuts with resource icons, update
* their timestamps.
*/
public void handlePackageUpdated(ShortcutService s, int newVersionCode) {
public void handlePackageUpdated(int newVersionCode) {
if (getPackageInfo().getVersionCode() >= newVersionCode) {
// Version hasn't changed; nothing to do.
return;
@@ -491,6 +497,8 @@ class ShortcutPackage extends ShortcutPackageItem {
getPackageInfo().setVersionCode(newVersionCode);
final ShortcutService s = mShortcutUser.mService;
boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -509,7 +517,7 @@ class ShortcutPackage extends ShortcutPackageItem {
}
}
public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.println();
pw.print(prefix);
@@ -522,7 +530,7 @@ class ShortcutPackage extends ShortcutPackageItem {
pw.print(prefix);
pw.print(" ");
pw.print("Calls: ");
pw.print(getApiCallCount(s));
pw.print(getApiCallCount());
pw.println();
// getApiCallCount() may have updated mLastKnownForegroundElapsedTime.
@@ -538,10 +546,10 @@ class ShortcutPackage extends ShortcutPackageItem {
pw.print("Last reset: [");
pw.print(mLastResetTime);
pw.print("] ");
pw.print(s.formatTime(mLastResetTime));
pw.print(ShortcutService.formatTime(mLastResetTime));
pw.println();
getPackageInfo().dump(s, pw, prefix + " ");
getPackageInfo().dump(pw, prefix + " ");
pw.println();
pw.print(prefix);
@@ -569,7 +577,7 @@ class ShortcutPackage extends ShortcutPackageItem {
pw.print("Total bitmap size: ");
pw.print(totalBitmapSize);
pw.print(" (");
pw.print(Formatter.formatFileSize(s.mContext, totalBitmapSize));
pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize));
pw.println(")");
}
@@ -651,7 +659,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final String packageName = ShortcutService.parseStringAttribute(parser,
ATTR_NAME);
final ShortcutPackage ret = new ShortcutPackage(s, shortcutUser,
final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
shortcutUser.getUserId(), packageName);
ret.mDynamicShortcutCount =

View File

@@ -189,7 +189,7 @@ class ShortcutPackageInfo {
mSigHashes = hashes;
}
public void dump(ShortcutService s, PrintWriter pw, String prefix) {
public void dump(PrintWriter pw, String prefix) {
pw.println();
pw.print(prefix);

View File

@@ -69,18 +69,20 @@ abstract class ShortcutPackageItem {
return mPackageInfo;
}
public void refreshPackageInfoAndSave(ShortcutService s) {
public void refreshPackageInfoAndSave() {
if (mPackageInfo.isShadow()) {
return; // Don't refresh for shadow user.
}
final ShortcutService s = mShortcutUser.mService;
mPackageInfo.refresh(s, this);
s.scheduleSaveUser(getOwnerUserId());
}
public void attemptToRestoreIfNeededAndSave(ShortcutService s) {
public void attemptToRestoreIfNeededAndSave() {
if (!mPackageInfo.isShadow()) {
return; // Already installed, nothing to do.
}
final ShortcutService s = mShortcutUser.mService;
if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package still not installed: %s user=%d",
@@ -91,14 +93,14 @@ abstract class ShortcutPackageItem {
if (!mPackageInfo.hasSignatures()) {
s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
+ " but signatures not found in the restore data.");
onRestoreBlocked(s);
onRestoreBlocked();
return;
}
final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
if (!mPackageInfo.canRestoreTo(s, pi)) {
// Package is now installed, but can't restore. Let the subclass do the cleanup.
onRestoreBlocked(s);
onRestoreBlocked();
return;
}
if (ShortcutService.DEBUG) {
@@ -106,7 +108,7 @@ abstract class ShortcutPackageItem {
mPackageUserId, getOwnerUserId()));
}
onRestored(s);
onRestored();
// Now the package is not shadow.
mPackageInfo.setShadow(false);
@@ -118,12 +120,12 @@ abstract class ShortcutPackageItem {
* Called when the new package can't be restored because it has a lower version number
* or different signatures.
*/
protected abstract void onRestoreBlocked(ShortcutService s);
protected abstract void onRestoreBlocked();
/**
* Called when the new package is successfully restored.
*/
protected abstract void onRestored(ShortcutService s);
protected abstract void onRestored();
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;

View File

@@ -74,7 +74,6 @@ 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;
@@ -104,6 +103,7 @@ import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -278,6 +278,8 @@ public class ShortcutService extends IShortcutService.Stub {
*/
private final AtomicLong mLocaleChangeSequenceNumber = new AtomicLong();
private final AtomicBoolean mBootCompleted = new AtomicBoolean();
private static final int PACKAGE_MATCH_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -427,6 +429,9 @@ public class ShortcutService extends IShortcutService.Stub {
case SystemService.PHASE_LOCK_SETTINGS_READY:
initialize();
break;
case SystemService.PHASE_BOOT_COMPLETED:
mBootCompleted.set(true);
break;
}
}
@@ -771,7 +776,7 @@ public class ShortcutService extends IShortcutService.Stub {
out.setOutput(bos, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
getUserShortcutsLocked(userId).saveToXml(this, out, forBackup);
getUserShortcutsLocked(userId).saveToXml(out, forBackup);
out.endDocument();
@@ -949,7 +954,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (userPackages == null) {
userPackages = loadUserLocked(userId);
if (userPackages == null) {
userPackages = new ShortcutUser(userId);
userPackages = new ShortcutUser(this, userId);
}
mUsers.put(userId, userPackages);
}
@@ -967,7 +972,7 @@ public class ShortcutService extends IShortcutService.Stub {
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
}
@GuardedBy("mLock")
@@ -976,7 +981,7 @@ public class ShortcutService extends IShortcutService.Stub {
@NonNull String packageName, @UserIdInt int ownerUserId,
@UserIdInt int launcherUserId) {
return getUserShortcutsLocked(ownerUserId)
.getLauncherShortcuts(this, packageName, launcherUserId);
.getLauncherShortcuts(packageName, launcherUserId);
}
// === Caller validation ===
@@ -1039,7 +1044,7 @@ public class ShortcutService extends IShortcutService.Stub {
private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
@NonNull String packageName, @NonNull File path) {
final ArraySet<String> usedFiles =
user.getPackageShortcuts(this, packageName).getUsedBitmapFiles();
user.getPackageShortcuts(packageName).getUsedBitmapFiles();
for (File child : path.listFiles()) {
if (!child.isFile()) {
@@ -1416,7 +1421,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
if (!ps.tryApiCall(this)) {
if (!ps.tryApiCall()) {
return false;
}
enforceMaxDynamicShortcuts(size);
@@ -1427,12 +1432,12 @@ public class ShortcutService extends IShortcutService.Stub {
}
// First, remove all un-pinned; dynamic shortcuts
ps.deleteAllDynamicShortcuts(this);
ps.deleteAllDynamicShortcuts();
// Then, add/update all. We need to make sure to take over "pinned" flag.
for (int i = 0; i < size; i++) {
final ShortcutInfo newShortcut = newShortcuts.get(i);
ps.addDynamicShortcut(this, newShortcut);
ps.addDynamicShortcut(newShortcut);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1451,7 +1456,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
if (!ps.tryApiCall(this)) {
if (!ps.tryApiCall()) {
return false;
}
@@ -1491,7 +1496,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
if (!ps.tryApiCall(this)) {
if (!ps.tryApiCall()) {
return false;
}
for (int i = 0; i < size; i++) {
@@ -1501,7 +1506,7 @@ public class ShortcutService extends IShortcutService.Stub {
fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
// Add it.
ps.addDynamicShortcut(this, newShortcut);
ps.addDynamicShortcut(newShortcut);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1517,7 +1522,7 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(this,
getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(
Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
}
}
@@ -1529,7 +1534,7 @@ public class ShortcutService extends IShortcutService.Stub {
verifyCaller(packageName, userId);
synchronized (mLock) {
getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
}
packageShortcutsChanged(packageName, userId);
}
@@ -1561,7 +1566,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ArrayList<ShortcutInfo> ret = new ArrayList<>();
getPackageShortcutsLocked(packageName, userId).findAll(this, ret, query, cloneFlags);
getPackageShortcutsLocked(packageName, userId).findAll(ret, query, cloneFlags);
return new ParceledListSlice<>(ret);
}
@@ -1580,7 +1585,7 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
return mMaxUpdatesPerInterval
- getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
- getPackageShortcutsLocked(packageName, userId).getApiCallCount();
}
}
@@ -1674,7 +1679,7 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.v(TAG, "Default launcher from PM: " + detected);
}
} else {
detected = user.getLauncherComponent();
detected = user.getDefaultLauncherComponent();
// TODO: Make sure it's still enabled.
if (DEBUG) {
@@ -1714,7 +1719,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (DEBUG) {
Slog.v(TAG, "Detected launcher: " + detected);
}
user.setLauncherComponent(this, detected);
user.setDefaultLauncherComponent(detected);
return detected.getPackageName().equals(callingPackage);
} else {
// Default launcher not found.
@@ -1748,7 +1753,7 @@ public class ShortcutService extends IShortcutService.Stub {
// First, remove the package from the package list (if the package is a publisher).
if (packageUserId == owningUserId) {
if (user.removePackage(this, packageName) != null) {
if (user.removePackage(packageName) != null) {
doNotify = true;
}
}
@@ -1761,7 +1766,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Now there may be orphan shortcuts because we removed pinned shortcuts at the previous
// step. Remove them too.
user.forAllPackages(p -> p.refreshPinnedFlags(this));
user.forAllPackages(p -> p.refreshPinnedFlags());
scheduleSaveUser(owningUserId);
@@ -1797,7 +1802,7 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
.attemptToRestoreIfNeededAndSave();
if (packageName != null) {
getShortcutsInnerLocked(launcherUserId,
@@ -1822,7 +1827,13 @@ public class ShortcutService extends IShortcutService.Stub {
final ArraySet<String> ids = shortcutIds == null ? null
: new ArraySet<>(shortcutIds);
getPackageShortcutsLocked(packageName, userId).findAll(ShortcutService.this, ret,
final ShortcutPackage p = getUserShortcutsLocked(userId)
.getPackageShortcutsIfExists(packageName);
if (p == null) {
return; // No need to instantiate ShortcutPackage.
}
p.findAll(ret,
(ShortcutInfo si) -> {
if (si.getLastChangedTimestamp() < changedSince) {
return false;
@@ -1854,7 +1865,7 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
.attemptToRestoreIfNeededAndSave();
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
@@ -1868,9 +1879,14 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkStringNotEmpty(packageName, "packageName");
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
final ShortcutPackage p = getUserShortcutsLocked(userId)
.getPackageShortcutsIfExists(packageName);
if (p == null) {
return null;
}
final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
getPackageShortcutsLocked(packageName, userId).findAll(
ShortcutService.this, list,
p.findAll(list,
(ShortcutInfo si) -> shortcutId.equals(si.getId()),
/* clone flags=*/ 0, callingPackage, launcherUserId);
return list.size() == 0 ? null : list.get(0);
@@ -1887,10 +1903,9 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
final ShortcutLauncher launcher =
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
launcher.attemptToRestoreIfNeededAndSave();
launcher.pinShortcuts(
ShortcutService.this, userId, packageName, shortcutIds);
launcher.pinShortcuts(userId, packageName, shortcutIds);
}
packageShortcutsChanged(packageName, userId);
}
@@ -1905,7 +1920,7 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
.attemptToRestoreIfNeededAndSave();
// Make sure the shortcut is actually visible to the launcher.
final ShortcutInfo si = getShortcutInfoLocked(
@@ -1934,10 +1949,15 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
.attemptToRestoreIfNeededAndSave();
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
packageName, userId).findShortcutById(shortcutId);
final ShortcutPackage p = getUserShortcutsLocked(userId)
.getPackageShortcutsIfExists(packageName);
if (p == null) {
return 0;
}
final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
return (shortcutInfo != null && shortcutInfo.hasIconResource())
? shortcutInfo.getIconResourceId() : 0;
}
@@ -1953,10 +1973,15 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
.attemptToRestoreIfNeededAndSave();
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
packageName, userId).findShortcutById(shortcutId);
final ShortcutPackage p = getUserShortcutsLocked(userId)
.getPackageShortcutsIfExists(packageName);
if (p == null) {
return null;
}
final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId);
if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
return null;
}
@@ -2000,8 +2025,14 @@ public class ShortcutService extends IShortcutService.Stub {
// This allows ShortcutUser's to detect the system locale change, so they can reset
// counters.
mLocaleChangeSequenceNumber.incrementAndGet();
postToHandler(() -> scheduleSaveBaseState());
// Ignore all callback during system boot.
if (mBootCompleted.get()) {
mLocaleChangeSequenceNumber.incrementAndGet();
if (DEBUG) {
Slog.d(TAG, "onSystemLocaleChangedNoLock: " + mLocaleChangeSequenceNumber.get());
}
postToHandler(() -> scheduleSaveBaseState());
}
}
}
@@ -2056,7 +2087,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (versionCode >= 0) {
// Package still installed, see if it's updated.
getUserShortcutsLocked(ownerUserId).handlePackageUpdated(
this, spi.getPackageName(), versionCode);
spi.getPackageName(), versionCode);
} else {
gonePackages.add(PackageWithUser.of(spi));
}
@@ -2093,7 +2124,7 @@ public class ShortcutService extends IShortcutService.Stub {
if (versionCode < 0) {
return; // shouldn't happen
}
getUserShortcutsLocked(userId).handlePackageUpdated(this, packageName, versionCode);
getUserShortcutsLocked(userId).handlePackageUpdated(packageName, versionCode);
}
}
@@ -2215,7 +2246,7 @@ public class ShortcutService extends IShortcutService.Stub {
return null;
}
user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave(this));
user.forAllPackageItems(spi -> spi.refreshPackageInfoAndSave());
// Then save.
final ByteArrayOutputStream os = new ByteArrayOutputStream(32 * 1024);
@@ -2336,7 +2367,7 @@ public class ShortcutService extends IShortcutService.Stub {
for (int i = 0; i < mUsers.size(); i++) {
pw.println();
mUsers.valueAt(i).dump(this, pw, " ");
mUsers.valueAt(i).dump(pw, " ");
}
pw.println();
@@ -2548,8 +2579,7 @@ public class ShortcutService extends IShortcutService.Stub {
private void clearLauncher() {
synchronized (mLock) {
getUserShortcutsLocked(mUserId).setLauncherComponent(
ShortcutService.this, null);
getUserShortcutsLocked(mUserId).setDefaultLauncherComponent(null);
}
}
@@ -2559,7 +2589,7 @@ public class ShortcutService extends IShortcutService.Stub {
hasShortcutHostPermissionInner("-", mUserId);
getOutPrintWriter().println("Launcher: "
+ getUserShortcutsLocked(mUserId).getLauncherComponent());
+ getUserShortcutsLocked(mUserId).getDefaultLauncherComponent());
}
}

View File

@@ -16,6 +16,7 @@
package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.text.format.Formatter;
@@ -87,6 +88,8 @@ class ShortcutUser {
}
}
final ShortcutService mService;
@UserIdInt
private final int mUserId;
@@ -97,11 +100,12 @@ class ShortcutUser {
private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
/** Default launcher that can access the launcher apps APIs. */
private ComponentName mLauncherComponent;
private ComponentName mDefaultLauncherComponent;
private long mKnownLocaleChangeSequenceNumber;
public ShortcutUser(int userId) {
public ShortcutUser(ShortcutService service, int userId) {
mService = service;
mUserId = userId;
}
@@ -120,10 +124,10 @@ class ShortcutUser {
return mPackages.containsKey(packageName);
}
public ShortcutPackage removePackage(@NonNull ShortcutService s, @NonNull String packageName) {
public ShortcutPackage removePackage(@NonNull String packageName) {
final ShortcutPackage removed = mPackages.remove(packageName);
s.cleanupBitmapsForPackage(mUserId, packageName);
mService.cleanupBitmapsForPackage(mUserId, packageName);
return removed;
}
@@ -140,23 +144,33 @@ class ShortcutUser {
launcher.getPackageName()), launcher);
}
@Nullable
public ShortcutLauncher removeLauncher(
@UserIdInt int packageUserId, @NonNull String packageName) {
return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
}
public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
ShortcutPackage ret = mPackages.get(packageName);
if (ret == null) {
ret = new ShortcutPackage(s, this, mUserId, packageName);
mPackages.put(packageName, ret);
} else {
ret.attemptToRestoreIfNeededAndSave(s);
@Nullable
public ShortcutPackage getPackageShortcutsIfExists(@NonNull String packageName) {
final ShortcutPackage ret = mPackages.get(packageName);
if (ret != null) {
ret.attemptToRestoreIfNeededAndSave();
}
return ret;
}
public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
@NonNull
public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
ShortcutPackage ret = getPackageShortcutsIfExists(packageName);
if (ret == null) {
ret = new ShortcutPackage(this, mUserId, packageName);
mPackages.put(packageName, ret);
}
return ret;
}
@NonNull
public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
@UserIdInt int launcherUserId) {
final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
ShortcutLauncher ret = mLaunchers.get(key);
@@ -164,7 +178,7 @@ class ShortcutUser {
ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
} else {
ret.attemptToRestoreIfNeededAndSave(s);
ret.attemptToRestoreIfNeededAndSave();
}
return ret;
}
@@ -201,8 +215,8 @@ 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();
public void resetThrottlingIfNeeded() {
final long currentNo = mService.getLocaleChangeSequenceNumber();
if (mKnownLocaleChangeSequenceNumber < currentNo) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, "LocaleChange detected for user " + mUserId);
@@ -210,31 +224,35 @@ class ShortcutUser {
mKnownLocaleChangeSequenceNumber = currentNo;
forAllPackages(p -> p.resetRateLimiting(s));
forAllPackages(p -> p.resetRateLimiting());
s.scheduleSaveUser(mUserId);
mService.scheduleSaveUser(mUserId);
}
}
/**
* Called when a package is updated.
*/
public void handlePackageUpdated(ShortcutService s, @NonNull String packageName,
public void handlePackageUpdated(@NonNull String packageName,
int newVersionCode) {
if (!mPackages.containsKey(packageName)) {
return;
}
getPackageShortcuts(s, packageName).handlePackageUpdated(s, newVersionCode);
final ShortcutPackage p = getPackageShortcutsIfExists(packageName);
if (p == null) {
return; // No need to instantiate ShortcutPackage.
}
p.handlePackageUpdated(newVersionCode);
}
public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
@UserIdInt int packageUserId) {
forPackageItem(packageName, packageUserId, spi -> {
spi.attemptToRestoreIfNeededAndSave(s);
spi.attemptToRestoreIfNeededAndSave();
});
}
public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
public void saveToXml(XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT);
@@ -242,29 +260,29 @@ class ShortcutUser {
mKnownLocaleChangeSequenceNumber);
ShortcutService.writeTagValue(out, TAG_LAUNCHER,
mLauncherComponent);
mDefaultLauncherComponent);
// Can't use forEachPackageItem due to the checked exceptions.
{
final int size = mLaunchers.size();
for (int i = 0; i < size; i++) {
saveShortcutPackageItem(s, out, mLaunchers.valueAt(i), forBackup);
saveShortcutPackageItem(out, mLaunchers.valueAt(i), forBackup);
}
}
{
final int size = mPackages.size();
for (int i = 0; i < size; i++) {
saveShortcutPackageItem(s, out, mPackages.valueAt(i), forBackup);
saveShortcutPackageItem(out, mPackages.valueAt(i), forBackup);
}
}
out.endTag(null, TAG_ROOT);
}
private void saveShortcutPackageItem(ShortcutService s, XmlSerializer out,
private void saveShortcutPackageItem(XmlSerializer out,
ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
if (forBackup) {
if (!s.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
return; // Don't save.
}
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
@@ -276,7 +294,7 @@ class ShortcutUser {
public static ShortcutUser loadFromXml(ShortcutService s, XmlPullParser parser, int userId,
boolean fromBackup) throws IOException, XmlPullParserException {
final ShortcutUser ret = new ShortcutUser(userId);
final ShortcutUser ret = new ShortcutUser(s, userId);
ret.mKnownLocaleChangeSequenceNumber = ShortcutService.parseLongAttribute(parser,
ATTR_KNOWN_LOCALE_CHANGE_SEQUENCE_NUMBER);
@@ -294,7 +312,7 @@ class ShortcutUser {
if (depth == outerDepth + 1) {
switch (tag) {
case TAG_LAUNCHER: {
ret.mLauncherComponent = ShortcutService.parseComponentNameAttribute(
ret.mDefaultLauncherComponent = ShortcutService.parseComponentNameAttribute(
parser, ATTR_VALUE);
continue;
}
@@ -319,16 +337,16 @@ class ShortcutUser {
return ret;
}
public ComponentName getLauncherComponent() {
return mLauncherComponent;
public ComponentName getDefaultLauncherComponent() {
return mDefaultLauncherComponent;
}
public void setLauncherComponent(ShortcutService s, ComponentName launcherComponent) {
if (Objects.equal(mLauncherComponent, launcherComponent)) {
public void setDefaultLauncherComponent(ComponentName launcherComponent) {
if (Objects.equal(mDefaultLauncherComponent, launcherComponent)) {
return;
}
mLauncherComponent = launcherComponent;
s.scheduleSaveUser(mUserId);
mDefaultLauncherComponent = launcherComponent;
mService.scheduleSaveUser(mUserId);
}
public void resetThrottling() {
@@ -337,7 +355,7 @@ class ShortcutUser {
}
}
public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
pw.print(prefix);
pw.print("User: ");
pw.print(mUserId);
@@ -349,24 +367,24 @@ class ShortcutUser {
pw.print(prefix);
pw.print("Default launcher: ");
pw.print(mLauncherComponent);
pw.print(mDefaultLauncherComponent);
pw.println();
for (int i = 0; i < mLaunchers.size(); i++) {
mLaunchers.valueAt(i).dump(s, pw, prefix);
mLaunchers.valueAt(i).dump(pw, prefix);
}
for (int i = 0; i < mPackages.size(); i++) {
mPackages.valueAt(i).dump(s, pw, prefix);
mPackages.valueAt(i).dump(pw, prefix);
}
pw.println();
pw.print(prefix);
pw.println("Bitmap directories: ");
dumpDirectorySize(s, pw, prefix + " ", s.getUserBitmapFilePath(mUserId));
dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId));
}
private void dumpDirectorySize(@NonNull ShortcutService s, @NonNull PrintWriter pw,
private void dumpDirectorySize(@NonNull PrintWriter pw,
@NonNull String prefix, File path) {
int numFiles = 0;
long size = 0;
@@ -377,7 +395,7 @@ class ShortcutUser {
numFiles++;
size += child.length();
} else if (child.isDirectory()) {
dumpDirectorySize(s, pw, prefix + " ", child);
dumpDirectorySize(pw, prefix + " ", child);
}
}
}
@@ -389,7 +407,7 @@ class ShortcutUser {
pw.print(" files, size=");
pw.print(size);
pw.print(" (");
pw.print(Formatter.formatFileSize(s.mContext, size));
pw.print(Formatter.formatFileSize(mService.mContext, size));
pw.println(")");
}
}

View File

@@ -3698,8 +3698,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
assertEquals(2, mManager.getRemainingCallCount());
});
mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setLauncherComponent(
mService, new ComponentName("pkg1", "class"));
mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM).setDefaultLauncherComponent(
new ComponentName("pkg1", "class"));
// Restore.
mService.saveDirtyInfo();
@@ -3732,7 +3732,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
});
assertEquals("pkg1", mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM)
.getLauncherComponent().getPackageName());
.getDefaultLauncherComponent().getPackageName());
// Start another user
mService.handleUnlockUser(USER_10);
@@ -3748,7 +3748,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
assertEquals("title10-1-1", getCallerShortcut("s1").getTitle());
assertEquals("title10-1-2", getCallerShortcut("s2").getTitle());
});
assertNull(mService.getShortcutsForTest().get(USER_10).getLauncherComponent());
assertNull(mService.getShortcutsForTest().get(USER_10).getDefaultLauncherComponent());
// Try stopping the user
mService.handleCleanupUser(USER_10);
@@ -5578,8 +5578,12 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
final long origSequenceNumber = mService.getLocaleChangeSequenceNumber();
// onSystemLocaleChangedNoLock before boot completed will be ignored.
mInternal.onSystemLocaleChangedNoLock();
assertEquals(origSequenceNumber, mService.getLocaleChangeSequenceNumber());
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mInternal.onSystemLocaleChangedNoLock();
assertEquals(origSequenceNumber + 1, mService.getLocaleChangeSequenceNumber());
// Note at this point only user-0 is loaded, and the counters are reset for this user,
@@ -5930,6 +5934,10 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
IllegalArgumentException.class,
"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(
IllegalArgumentException.class,
"title must be provided",