Loading core/java/android/content/pm/ShortcutInfo.java +19 −10 Original line number Diff line number Diff line Loading @@ -709,7 +709,7 @@ public final class ShortcutInfo implements Parcelable { @NonNull @Deprecated public Builder setId(@NonNull String id) { mId = Preconditions.checkStringNotEmpty(id, "id"); mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); return this; } Loading @@ -721,14 +721,17 @@ public final class ShortcutInfo implements Parcelable { */ public Builder(Context context, String id) { mContext = context; mId = Preconditions.checkStringNotEmpty(id, "id"); mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); } /** * Sets the target activity. A shortcut will be shown with this activity on the launcher. * * <p>This is a mandatory field, unless it's passed to * {@link ShortcutManager#updateShortcuts(List)}. * <p>Only "main" activities -- i.e. ones with an intent filter for * {@link Intent#ACTION_MAIN} and {@link Intent#CATEGORY_LAUNCHER} can be target activities. * * <p>By default, the first main activity defined in the application manifest will be * the target. * * <p>The package name of the target activity must match the package name of the shortcut * publisher. Loading @@ -738,7 +741,7 @@ public final class ShortcutInfo implements Parcelable { */ @NonNull public Builder setActivity(@NonNull ComponentName activity) { mActivity = Preconditions.checkNotNull(activity, "activity"); mActivity = Preconditions.checkNotNull(activity, "activity cannot be null"); return this; } Loading Loading @@ -785,7 +788,7 @@ public final class ShortcutInfo implements Parcelable { @NonNull public Builder setShortLabel(@NonNull CharSequence shortLabel) { Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set"); mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel"); mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty"); return this; } Loading @@ -810,7 +813,7 @@ public final class ShortcutInfo implements Parcelable { @NonNull public Builder setLongLabel(@NonNull CharSequence longLabel) { Preconditions.checkState(mTextResId == 0, "longLabelResId already set"); mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel"); mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty"); return this; } Loading Loading @@ -854,7 +857,8 @@ public final class ShortcutInfo implements Parcelable { Preconditions.checkState( mDisabledMessageResId == 0, "disabledMessageResId already set"); mDisabledMessage = Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage"); Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage cannot be empty"); return this; } Loading @@ -876,8 +880,8 @@ public final class ShortcutInfo implements Parcelable { */ @NonNull public Builder setIntent(@NonNull Intent intent) { mIntent = Preconditions.checkNotNull(intent, "intent"); Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set"); mIntent = Preconditions.checkNotNull(intent, "intent cannot be null"); Preconditions.checkNotNull(mIntent.getAction(), "intent's action must be set"); return this; } Loading Loading @@ -944,6 +948,11 @@ public final class ShortcutInfo implements Parcelable { return mActivity; } /** @hide */ public void setActivity(ComponentName activity) { mActivity = activity; } /** * Icon. * Loading services/core/java/com/android/server/pm/ShortcutPackage.java +38 −9 Original line number Diff line number Diff line Loading @@ -54,8 +54,6 @@ import java.util.function.Predicate; * User information used by {@link ShortcutService}. * * All methods should be guarded by {@code #mShortcutUser.mService.mLock}. * * TODO Max dynamic shortcuts cap should be per activity. */ class ShortcutPackage extends ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; Loading Loading @@ -321,9 +319,27 @@ class ShortcutPackage extends ShortcutPackageItem { /** * Remove a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut * is pinned, it'll remain as a pinned shortcut, and is still enabled. * * @return true if it's actually removed because it wasn't pinned, or false if it's still * pinned. */ public boolean deleteDynamicWithId(@NonNull String shortcutId) { final ShortcutInfo removed = deleteOrDisableWithId( shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false); return removed == null; } /** * Disable a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut * is pinned, it'll remain as a pinned shortcut, but will be disabled. * * @return true if it's actually removed because it wasn't pinned, or false if it's still * pinned. */ public void deleteDynamicWithId(@NonNull String shortcutId) { deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false); private boolean disableDynamicWithId(@NonNull String shortcutId) { final ShortcutInfo disabled = deleteOrDisableWithId( shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false); return disabled == null; } /** Loading Loading @@ -599,14 +615,14 @@ class ShortcutPackage extends ShortcutPackageItem { * * @return TRUE if any shortcuts have been changed. */ public boolean handlePackageAddedOrUpdated(boolean isNewApp) { public boolean handlePackageAddedOrUpdated(boolean isNewApp, boolean forceRescan) { final PackageInfo pi = mShortcutUser.mService.getPackageInfo( getPackageName(), getPackageUserId()); if (pi == null) { return false; // Shouldn't happen. } if (!isNewApp) { if (!isNewApp && !forceRescan) { // Make sure the version code or last update time has changed. // Otherwise, nothing to do. if (getPackageInfo().getVersionCode() >= pi.versionCode Loading Loading @@ -649,12 +665,26 @@ class ShortcutPackage extends ShortcutPackageItem { boolean changed = false; // For existing shortcuts, update timestamps if they have any resources. // Also check if shortcuts' activities are still main activities. Otherwise, disable them. if (!isNewApp) { Resources publisherRes = null; for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); if (si.isDynamic()) { if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) { Slog.w(TAG, String.format( "%s is no longer main activity. Disabling shorcut %s.", getPackageName(), si.getId())); if (disableDynamicWithId(si.getId())) { continue; // Actually removed. } // Still pinned, so fall-through and possibly update the resources. } changed = true; } if (si.hasAnyResources()) { if (!si.isOriginallyFromManifest()) { if (publisherRes == null) { Loading Loading @@ -912,9 +942,8 @@ class ShortcutPackage extends ShortcutPackageItem { final ComponentName newActivity = newShortcut.getActivity(); if (newActivity == null) { if (operation != ShortcutService.OPERATION_UPDATE) { // This method may be called before validating shortcuts, so this may happen, // and is a caller side error. throw new NullPointerException("Activity must be provided"); service.wtf("Activity must not be null at this point"); continue; // Just ignore this invalid case. } continue; // Activity can be null for update. } Loading services/core/java/com/android/server/pm/ShortcutParser.java +30 −6 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; Loading Loading @@ -58,18 +59,36 @@ public class ShortcutParser { @Nullable public static List<ShortcutInfo> parseShortcuts(ShortcutService service, String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException { final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId); if (ShortcutService.DEBUG) { Slog.d(TAG, String.format("Scanning package %s for manifest shortcuts on user %d", packageName, userId)); } final List<ResolveInfo> activities = service.injectGetMainActivities(packageName, userId); if (activities == null || activities.size() == 0) { return null; } List<ShortcutInfo> result = null; try { if (pi != null && pi.activities != null) { for (ActivityInfo activityInfo : pi.activities) { result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result); final int size = activities.size(); for (int i = 0; i < size; i++) { final ActivityInfo activityInfoNoMetadata = activities.get(i).activityInfo; if (activityInfoNoMetadata == null) { continue; } final ActivityInfo activityInfoWithMetadata = service.injectGetActivityInfoWithMetadata( activityInfoNoMetadata.getComponentName(), userId); if (activityInfoWithMetadata != null) { result = parseShortcutsOneFile( service, activityInfoWithMetadata, packageName, userId, result); } } } catch (RuntimeException e) { // Resource ID mismatch may cause various runtime exceptions when parsing XMLs. // Resource ID mismatch may cause various runtime exceptions when parsing XMLs, // But we don't crash the device, so just swallow them. service.wtf( "Exception caught while parsing shortcut XML for package=" + packageName, e); return null; Loading @@ -81,6 +100,11 @@ public class ShortcutParser { ShortcutService service, ActivityInfo activityInfo, String packageName, @UserIdInt int userId, List<ShortcutInfo> result) throws IOException, XmlPullParserException { if (ShortcutService.DEBUG) { Slog.d(TAG, String.format( "Checking main activity %s", activityInfo.getComponentName())); } XmlResourceParser parser = null; try { parser = service.injectXmlMetaData(activityInfo, METADATA_KEY); Loading Loading @@ -223,7 +247,7 @@ public class ShortcutParser { continue; } Log.w(TAG, "Unknown tag " + tag + " at depth " + depth); ShortcutService.warnForInvalidTag(depth, tag); } } finally { if (parser != null) { Loading services/core/java/com/android/server/pm/ShortcutService.java +230 −51 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/pm/ShortcutUser.java +6 −6 Original line number Diff line number Diff line Loading @@ -88,7 +88,7 @@ class ShortcutUser { @Override public String toString() { return String.format("{Package: %d, %s}", userId, packageName); return String.format("[Package: %d, %s]", userId, packageName); } } Loading @@ -99,8 +99,6 @@ class ShortcutUser { private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); private final SparseArray<ShortcutPackage> mPackagesFromUid = new SparseArray<>(); private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); /** Default launcher that can access the launcher apps APIs. */ Loading Loading @@ -244,12 +242,12 @@ class ShortcutUser { } } public void handlePackageAddedOrUpdated(@NonNull String packageName) { public void handlePackageAddedOrUpdated(@NonNull String packageName, boolean forceRescan) { final boolean isNewApp = !mPackages.containsKey(packageName); final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName); if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) { if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp, forceRescan)) { if (isNewApp) { mPackages.remove(packageName); } Loading Loading @@ -381,8 +379,10 @@ class ShortcutUser { pw.print(mUserId); pw.print(" Known locale seq#: "); pw.print(mKnownLocaleChangeSequenceNumber); pw.print(" Last app scan: "); pw.print(" Last app scan: ["); pw.print(mLastAppScanTime); pw.print("] "); pw.print(ShortcutService.formatTime(mLastAppScanTime)); pw.println(); prefix += prefix + " "; Loading Loading
core/java/android/content/pm/ShortcutInfo.java +19 −10 Original line number Diff line number Diff line Loading @@ -709,7 +709,7 @@ public final class ShortcutInfo implements Parcelable { @NonNull @Deprecated public Builder setId(@NonNull String id) { mId = Preconditions.checkStringNotEmpty(id, "id"); mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); return this; } Loading @@ -721,14 +721,17 @@ public final class ShortcutInfo implements Parcelable { */ public Builder(Context context, String id) { mContext = context; mId = Preconditions.checkStringNotEmpty(id, "id"); mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); } /** * Sets the target activity. A shortcut will be shown with this activity on the launcher. * * <p>This is a mandatory field, unless it's passed to * {@link ShortcutManager#updateShortcuts(List)}. * <p>Only "main" activities -- i.e. ones with an intent filter for * {@link Intent#ACTION_MAIN} and {@link Intent#CATEGORY_LAUNCHER} can be target activities. * * <p>By default, the first main activity defined in the application manifest will be * the target. * * <p>The package name of the target activity must match the package name of the shortcut * publisher. Loading @@ -738,7 +741,7 @@ public final class ShortcutInfo implements Parcelable { */ @NonNull public Builder setActivity(@NonNull ComponentName activity) { mActivity = Preconditions.checkNotNull(activity, "activity"); mActivity = Preconditions.checkNotNull(activity, "activity cannot be null"); return this; } Loading Loading @@ -785,7 +788,7 @@ public final class ShortcutInfo implements Parcelable { @NonNull public Builder setShortLabel(@NonNull CharSequence shortLabel) { Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set"); mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel"); mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty"); return this; } Loading @@ -810,7 +813,7 @@ public final class ShortcutInfo implements Parcelable { @NonNull public Builder setLongLabel(@NonNull CharSequence longLabel) { Preconditions.checkState(mTextResId == 0, "longLabelResId already set"); mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel"); mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty"); return this; } Loading Loading @@ -854,7 +857,8 @@ public final class ShortcutInfo implements Parcelable { Preconditions.checkState( mDisabledMessageResId == 0, "disabledMessageResId already set"); mDisabledMessage = Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage"); Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage cannot be empty"); return this; } Loading @@ -876,8 +880,8 @@ public final class ShortcutInfo implements Parcelable { */ @NonNull public Builder setIntent(@NonNull Intent intent) { mIntent = Preconditions.checkNotNull(intent, "intent"); Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set"); mIntent = Preconditions.checkNotNull(intent, "intent cannot be null"); Preconditions.checkNotNull(mIntent.getAction(), "intent's action must be set"); return this; } Loading Loading @@ -944,6 +948,11 @@ public final class ShortcutInfo implements Parcelable { return mActivity; } /** @hide */ public void setActivity(ComponentName activity) { mActivity = activity; } /** * Icon. * Loading
services/core/java/com/android/server/pm/ShortcutPackage.java +38 −9 Original line number Diff line number Diff line Loading @@ -54,8 +54,6 @@ import java.util.function.Predicate; * User information used by {@link ShortcutService}. * * All methods should be guarded by {@code #mShortcutUser.mService.mLock}. * * TODO Max dynamic shortcuts cap should be per activity. */ class ShortcutPackage extends ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; Loading Loading @@ -321,9 +319,27 @@ class ShortcutPackage extends ShortcutPackageItem { /** * Remove a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut * is pinned, it'll remain as a pinned shortcut, and is still enabled. * * @return true if it's actually removed because it wasn't pinned, or false if it's still * pinned. */ public boolean deleteDynamicWithId(@NonNull String shortcutId) { final ShortcutInfo removed = deleteOrDisableWithId( shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false); return removed == null; } /** * Disable a dynamic shortcut by ID. It'll be removed from the dynamic set, but if the shortcut * is pinned, it'll remain as a pinned shortcut, but will be disabled. * * @return true if it's actually removed because it wasn't pinned, or false if it's still * pinned. */ public void deleteDynamicWithId(@NonNull String shortcutId) { deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false); private boolean disableDynamicWithId(@NonNull String shortcutId) { final ShortcutInfo disabled = deleteOrDisableWithId( shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false); return disabled == null; } /** Loading Loading @@ -599,14 +615,14 @@ class ShortcutPackage extends ShortcutPackageItem { * * @return TRUE if any shortcuts have been changed. */ public boolean handlePackageAddedOrUpdated(boolean isNewApp) { public boolean handlePackageAddedOrUpdated(boolean isNewApp, boolean forceRescan) { final PackageInfo pi = mShortcutUser.mService.getPackageInfo( getPackageName(), getPackageUserId()); if (pi == null) { return false; // Shouldn't happen. } if (!isNewApp) { if (!isNewApp && !forceRescan) { // Make sure the version code or last update time has changed. // Otherwise, nothing to do. if (getPackageInfo().getVersionCode() >= pi.versionCode Loading Loading @@ -649,12 +665,26 @@ class ShortcutPackage extends ShortcutPackageItem { boolean changed = false; // For existing shortcuts, update timestamps if they have any resources. // Also check if shortcuts' activities are still main activities. Otherwise, disable them. if (!isNewApp) { Resources publisherRes = null; for (int i = mShortcuts.size() - 1; i >= 0; i--) { final ShortcutInfo si = mShortcuts.valueAt(i); if (si.isDynamic()) { if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) { Slog.w(TAG, String.format( "%s is no longer main activity. Disabling shorcut %s.", getPackageName(), si.getId())); if (disableDynamicWithId(si.getId())) { continue; // Actually removed. } // Still pinned, so fall-through and possibly update the resources. } changed = true; } if (si.hasAnyResources()) { if (!si.isOriginallyFromManifest()) { if (publisherRes == null) { Loading Loading @@ -912,9 +942,8 @@ class ShortcutPackage extends ShortcutPackageItem { final ComponentName newActivity = newShortcut.getActivity(); if (newActivity == null) { if (operation != ShortcutService.OPERATION_UPDATE) { // This method may be called before validating shortcuts, so this may happen, // and is a caller side error. throw new NullPointerException("Activity must be provided"); service.wtf("Activity must not be null at this point"); continue; // Just ignore this invalid case. } continue; // Activity can be null for update. } Loading
services/core/java/com/android/server/pm/ShortcutParser.java +30 −6 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; Loading Loading @@ -58,18 +59,36 @@ public class ShortcutParser { @Nullable public static List<ShortcutInfo> parseShortcuts(ShortcutService service, String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException { final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId); if (ShortcutService.DEBUG) { Slog.d(TAG, String.format("Scanning package %s for manifest shortcuts on user %d", packageName, userId)); } final List<ResolveInfo> activities = service.injectGetMainActivities(packageName, userId); if (activities == null || activities.size() == 0) { return null; } List<ShortcutInfo> result = null; try { if (pi != null && pi.activities != null) { for (ActivityInfo activityInfo : pi.activities) { result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result); final int size = activities.size(); for (int i = 0; i < size; i++) { final ActivityInfo activityInfoNoMetadata = activities.get(i).activityInfo; if (activityInfoNoMetadata == null) { continue; } final ActivityInfo activityInfoWithMetadata = service.injectGetActivityInfoWithMetadata( activityInfoNoMetadata.getComponentName(), userId); if (activityInfoWithMetadata != null) { result = parseShortcutsOneFile( service, activityInfoWithMetadata, packageName, userId, result); } } } catch (RuntimeException e) { // Resource ID mismatch may cause various runtime exceptions when parsing XMLs. // Resource ID mismatch may cause various runtime exceptions when parsing XMLs, // But we don't crash the device, so just swallow them. service.wtf( "Exception caught while parsing shortcut XML for package=" + packageName, e); return null; Loading @@ -81,6 +100,11 @@ public class ShortcutParser { ShortcutService service, ActivityInfo activityInfo, String packageName, @UserIdInt int userId, List<ShortcutInfo> result) throws IOException, XmlPullParserException { if (ShortcutService.DEBUG) { Slog.d(TAG, String.format( "Checking main activity %s", activityInfo.getComponentName())); } XmlResourceParser parser = null; try { parser = service.injectXmlMetaData(activityInfo, METADATA_KEY); Loading Loading @@ -223,7 +247,7 @@ public class ShortcutParser { continue; } Log.w(TAG, "Unknown tag " + tag + " at depth " + depth); ShortcutService.warnForInvalidTag(depth, tag); } } finally { if (parser != null) { Loading
services/core/java/com/android/server/pm/ShortcutService.java +230 −51 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/pm/ShortcutUser.java +6 −6 Original line number Diff line number Diff line Loading @@ -88,7 +88,7 @@ class ShortcutUser { @Override public String toString() { return String.format("{Package: %d, %s}", userId, packageName); return String.format("[Package: %d, %s]", userId, packageName); } } Loading @@ -99,8 +99,6 @@ class ShortcutUser { private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); private final SparseArray<ShortcutPackage> mPackagesFromUid = new SparseArray<>(); private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); /** Default launcher that can access the launcher apps APIs. */ Loading Loading @@ -244,12 +242,12 @@ class ShortcutUser { } } public void handlePackageAddedOrUpdated(@NonNull String packageName) { public void handlePackageAddedOrUpdated(@NonNull String packageName, boolean forceRescan) { final boolean isNewApp = !mPackages.containsKey(packageName); final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName); if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) { if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp, forceRescan)) { if (isNewApp) { mPackages.remove(packageName); } Loading Loading @@ -381,8 +379,10 @@ class ShortcutUser { pw.print(mUserId); pw.print(" Known locale seq#: "); pw.print(mKnownLocaleChangeSequenceNumber); pw.print(" Last app scan: "); pw.print(" Last app scan: ["); pw.print(mLastAppScanTime); pw.print("] "); pw.print(ShortcutService.formatTime(mLastAppScanTime)); pw.println(); prefix += prefix + " "; Loading