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
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index bd8cae20..988ba44 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -439,6 +439,7 @@
@NonNull
public Builder setIntent(@NonNull Intent intent) {
mIntent = Preconditions.checkNotNull(intent, "intent");
+ Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set.");
return this;
}
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index ab0367d..c12137a 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -18,7 +18,6 @@
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 @@
*
* <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 @@
}
/**
- * 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 @@
}
/**
- * Delete a single dynamic shortcut by ID.
+ * Delete dynamic shortcuts by ID.
*/
public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) {
try {
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 76d47a8..db2b9f4 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -80,26 +80,31 @@
* 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 @@
}
mPinnedShortcuts.put(pu, newSet);
}
- packageShortcuts.refreshPinnedFlags(s);
+ packageShortcuts.refreshPinnedFlags();
}
/**
@@ -240,7 +245,7 @@
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 @@
pw.print(getOwnerUserId());
pw.println();
- getPackageInfo().dump(s, pw, prefix + " ");
+ getPackageInfo().dump(pw, prefix + " ");
pw.println();
final int size = mPinnedShortcuts.size();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index f3ce79c..1b08663 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -98,17 +98,16 @@
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 @@
* 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 @@
}
@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 @@
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 @@
*
* 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 @@
}
// 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 @@
}
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 @@
/**
* 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 @@
if (oldShortcut.isPinned()) {
oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
} else {
- deleteShortcut(s, shortcutId);
+ deleteShortcut(shortcutId);
}
}
@@ -279,14 +278,15 @@
*
* <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 @@
});
// Lastly, remove the ones that are no longer pinned nor dynamic.
- removeOrphans(s);
+ removeOrphans();
}
/**
@@ -317,8 +317,10 @@
* <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 @@
|| mLastKnownForegroundElapsedTime
< s.getUidLastForegroundElapsedTimeLocked(mPackageUid)) {
mLastKnownForegroundElapsedTime = s.injectElapsedRealtime();
- resetRateLimiting(s);
+ resetRateLimiting();
}
// Note resetThrottlingIfNeeded() and resetRateLimiting() will set 0 to mApiCallCount,
@@ -349,8 +351,8 @@
// 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 @@
* <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 @@
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 @@
/**
* 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 @@
* 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 @@
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 @@
* 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 =
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 74969f0..ae9709e 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -189,7 +189,7 @@
mSigHashes = hashes;
}
- public void dump(ShortcutService s, PrintWriter pw, String prefix) {
+ public void dump(PrintWriter pw, String prefix) {
pw.println();
pw.print(prefix);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 6fbdb82..0c2417c 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -69,18 +69,20 @@
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 @@
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 @@
mPackageUserId, getOwnerUserId()));
}
- onRestored(s);
+ onRestored();
// Now the package is not shadow.
mPackageInfo.setShadow(false);
@@ -118,12 +120,12 @@
* 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;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 66833f46..5219201 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -74,7 +74,6 @@
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.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 @@
*/
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 @@
case SystemService.PHASE_LOCK_SETTINGS_READY:
initialize();
break;
+ case SystemService.PHASE_BOOT_COMPLETED:
+ mBootCompleted.set(true);
+ break;
}
}
@@ -771,7 +776,7 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
- if (!ps.tryApiCall(this)) {
+ if (!ps.tryApiCall()) {
return false;
}
enforceMaxDynamicShortcuts(size);
@@ -1427,12 +1432,12 @@
}
// 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 @@
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
// Throttling.
- if (!ps.tryApiCall(this)) {
+ if (!ps.tryApiCall()) {
return false;
}
@@ -1491,7 +1496,7 @@
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 @@
fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
// Add it.
- ps.addDynamicShortcut(this, newShortcut);
+ ps.addDynamicShortcut(newShortcut);
}
}
packageShortcutsChanged(packageName, userId);
@@ -1517,7 +1522,7 @@
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 @@
verifyCaller(packageName, userId);
synchronized (mLock) {
- getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
+ getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts();
}
packageShortcutsChanged(packageName, userId);
}
@@ -1561,7 +1566,7 @@
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 @@
synchronized (mLock) {
return mMaxUpdatesPerInterval
- - getPackageShortcutsLocked(packageName, userId).getApiCallCount(this);
+ - getPackageShortcutsLocked(packageName, userId).getApiCallCount();
}
}
@@ -1674,7 +1679,7 @@
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 @@
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 @@
// 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 @@
// 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 @@
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
- .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+ .attemptToRestoreIfNeededAndSave();
if (packageName != null) {
getShortcutsInnerLocked(launcherUserId,
@@ -1822,7 +1827,13 @@
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 @@
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
- .attemptToRestoreIfNeededAndSave(ShortcutService.this);
+ .attemptToRestoreIfNeededAndSave();
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
@@ -1868,9 +1879,14 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
if (versionCode < 0) {
return; // shouldn't happen
}
- getUserShortcutsLocked(userId).handlePackageUpdated(this, packageName, versionCode);
+ getUserShortcutsLocked(userId).handlePackageUpdated(packageName, versionCode);
}
}
@@ -2215,7 +2246,7 @@
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 @@
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 @@
private void clearLauncher() {
synchronized (mLock) {
- getUserShortcutsLocked(mUserId).setLauncherComponent(
- ShortcutService.this, null);
+ getUserShortcutsLocked(mUserId).setDefaultLauncherComponent(null);
}
}
@@ -2559,7 +2589,7 @@
hasShortcutHostPermissionInner("-", mUserId);
getOutPrintWriter().println("Launcher: "
- + getUserShortcutsLocked(mUserId).getLauncherComponent());
+ + getUserShortcutsLocked(mUserId).getDefaultLauncherComponent());
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 2efae86..d38cfba 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -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 @@
}
}
+ final ShortcutService mService;
+
@UserIdInt
private final int mUserId;
@@ -97,11 +100,12 @@
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 @@
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 @@
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 @@
ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
} else {
- ret.attemptToRestoreIfNeededAndSave(s);
+ ret.attemptToRestoreIfNeededAndSave();
}
return ret;
}
@@ -201,8 +215,8 @@
/**
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
numFiles++;
size += child.length();
} else if (child.isDirectory()) {
- dumpDirectorySize(s, pw, prefix + " ", child);
+ dumpDirectorySize(pw, prefix + " ", child);
}
}
}
@@ -389,7 +407,7 @@
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(")");
}
}
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 7fa0c33..918fdab 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java
@@ -3698,8 +3698,8 @@
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 @@
});
assertEquals("pkg1", mService.getShortcutsForTest().get(UserHandle.USER_SYSTEM)
- .getLauncherComponent().getPackageName());
+ .getDefaultLauncherComponent().getPackageName());
// Start another user
mService.handleUnlockUser(USER_10);
@@ -3748,7 +3748,7 @@
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 @@
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,
@@ -5931,6 +5935,10 @@
"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",
() -> new ShortcutInfo.Builder(getTestContext()).setId("id").build()