diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index c0b82b4dfee3f..61b0eb0b51f8f 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -36,161 +36,72 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.List; /** - * The ShortcutManager manages an app's shortcuts. Shortcuts provide users - * with quick access to activities other than an app's main activity in the currently-active - * launcher. For example, - * an email app may publish the "compose new email" action, which will directly open the - * compose activity. The {@link ShortcutInfo} class contains information about each of the - * shortcuts themselves. + * The ShortcutManager manages an app's shortcuts. Shortcuts provide users with quick + * access to activities other than an app's main activity in the currently-active launcher, provided + * that the launcher supports app shortcuts. For example, an email app may publish the "compose new + * email" action, which will directly open the compose activity. The {@link ShortcutInfo} class + * contains information about each of the shortcuts themselves. * - *
This page discusses the implementation details of the ShortcutManager class. For
+ * guidance on performing operations on app shortcuts within your app, see the
+ * App Shortcuts feature guide.
*
- *
- * There are several different types of shortcuts: + *
Static shortcuts are declared in a resource XML file, which is referenced in the publisher
- * app's AndroidManifest.xml file. These shortcuts are visually associated with an
- * app's launcher icon.
- *
Static shortcuts are published when an app is installed, and the details of these shortcuts - * change when an app is upgraded with an updated XML file. Static shortcuts are immutable, and - * their definitions, such as icons and labels, cannot be changed dynamically without upgrading the - * publisher app.
Important security note: All shortcut information is stored in + * credential encrypted storage, so your app + * cannot access a user's shortcuts until after they've unlocked the device. * - *
Only main activities—activities that handle the {@code MAIN} action and the - * {@code LAUNCHER} category—can have shortcuts. - * If an app has multiple main activities, these activities have different sets - * of shortcuts. + *
Static shortcuts and dynamic shortcuts are shown in a supported launcher when the user - * long-presses on an app's launcher icon. Note that the actual gesture may be different - * depending on the launcher app. + * performs a specific gesture. On currently-supported launchers, the gesture is a long-press on the + * app's launcher icon, but the actual gesture may be different on other launcher apps. * - *
Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of - * static and dynamic shortcuts combined. + *
The {@link LauncherApps} class provides APIs for launcher apps to access shortcuts. * + *
Because pinned shortcuts appear in the launcher itself, they're always visible. A pinned + * shortcut is removed from the launcher only in the following situations: + *
Apps running in the foreground can also pin shortcuts at runtime, subject to user - * permission, using this class's APIs. Each pinned shortcut is a copy of a static shortcut or a - * dynamic shortcut. Although users can pin a shortcut multiple times, the system calls the pinning - * API only once to complete the pinning process. Unlike static and dynamic shortcuts, pinned - * shortcuts appear as separate icons, visually distinct from the app's launcher icon, in the - * launcher. There is no limit to the number of pinned shortcuts that an app can create. + *
Because the system performs + * backup and restore on pinned + * shortcuts automatically, these shortcuts' IDs should contain either stable, constant strings or + * server-side identifiers, rather than identifiers generated locally that might not make sense on + * other devices. * - *
Pinned shortcuts cannot be removed by publisher apps. They're removed only - * when the user removes them, when the publisher app is uninstalled, or when the user performs the - * clear data action on the publisher app from the device's Settings app. + *
However, the publisher app can disable pinned shortcuts so they cannot be started. - * See the following sections for details. + *
When the launcher displays an app's shortcuts, they should appear in the following order: * - *
When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut, - * the pinned shortcut will still be visible and launchable. This allows an app to have - * more than {@link #getMaxShortcutCountPerActivity()} number of shortcuts. + *
Shortcut ranks are non-negative, sequential integers that determine the order in which + * shortcuts appear, assuming that the shortcuts are all in the same category. You can update ranks + * of existing shortcuts when you call {@link #updateShortcuts(List)}, + * {@link #addDynamicShortcuts(List)}, or {@link #setDynamicShortcuts(List)}. * - *
For example, suppose {@link #getMaxShortcutCountPerActivity()} is 5: - *
Note: Ranks are auto-adjusted so that they're unique for each type of + * shortcut (static or dynamic). For example, if there are 3 dynamic shortcuts with ranks 0, 1 and + * 2, adding another dynamic shortcut with a rank of 1 represents a request to place this shortcut + * at the second position. In response, the third and fourth shortcuts move closer to the bottom of + * the shortcut list, with their ranks changing to 2 and 3, respectively. * - *
When an app is upgraded and the new version - * no longer uses a static shortcut that appeared in the previous version, this deprecated - * shortcut isn't published as a static shortcut. - * - *
If the deprecated shortcut is pinned, then the pinned shortcut will remain on the launcher, - * but it's disabled automatically. When a pinned shortcut is disabled, this class's APIs cannot - * update it. - * - *
- * In order to add static shortcuts to your app, first add
- * {@code
- *<manifest xmlns:android="http://schemas.android.com/apk/res/android" - * package="com.example.myapplication"> - * <application ... > - * <activity android:name="Main"> - * <intent-filter> - * <action android:name="android.intent.action.MAIN" /> - * <category android:name="android.intent.category.LAUNCHER" /> - * </intent-filter> - * <meta-data android:name="android.app.shortcuts" - * android:resource="@xml/shortcuts" /> - * </activity> - * </application> - *</manifest> - *- * - * Then, define your app's static shortcuts in the
res/xml/shortcuts.xml
- * file:
- * - *<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> - * <shortcut - * android:shortcutId="compose" - * android:enabled="true" - * android:icon="@drawable/compose_icon" - * android:shortcutShortLabel="@string/compose_shortcut_short_label1" - * android:shortcutLongLabel="@string/compose_shortcut_long_label1" - * android:shortcutDisabledMessage="@string/compose_disabled_message1"> - * <intent - * android:action="android.intent.action.VIEW" - * android:targetPackage="com.example.myapplication" - * android:targetClass="com.example.myapplication.ComposeActivity" /> - * <!-- If your shortcut is associated with multiple intents, include them - * here. The last intent in the list is what the user sees when they - * launch this shortcut. --> - * <categories android:name="android.shortcut.conversation" /> - * </shortcut> - * <!-- Specify more shortcuts here. --> - *</shortcuts> - *+ *
You can provide multiple intents for a single shortcut so that the last defined activity is + * launched with the other activities in the + * back stack. See + * {@link android.app.TaskStackBuilder} for details. *
Note: String resources may not be used within an {@code
- * Apps can publish dynamic shortcuts with {@link #setDynamicShortcuts(List)} - * or {@link #addDynamicShortcuts(List)}. The {@link #updateShortcuts(List)} method can also be - * used to update existing, mutable shortcuts. - * Use {@link #removeDynamicShortcuts(List)} or {@link #removeAllDynamicShortcuts()} to remove - * dynamic shortcuts. - * - *
The following code snippet shows how to create a single dynamic shortcut: - *
- *ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
- *
- *ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "id1")
- * .setShortLabel("Web site")
- * .setLongLabel("Open the web site")
- * .setIcon(Icon.createWithResource(context, R.drawable.icon_website))
- * .setIntent(new Intent(Intent.ACTION_VIEW,
- * Uri.parse("https://www.mysite.example.com/")))
- * .build();
- *
- *shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
- *
- *
- * Apps can pin an existing shortcut (either static or dynamic) or an entirely new shortcut to a - * supported launcher programatically using {@link #requestPinShortcut(ShortcutInfo, IntentSender)}. - * You pass two arguments into this method: - * - *
A {@link android.app.PendingIntent} object – This intent represents the callback - * that your app receives if the shortcut is successfully pinned to the device's launcher. - *
Note: If the user doesn't allow the shortcut to be pinned to the launcher, the - * pinning process fails, and the {@link Intent} object that is passed into this - * {@link android.app.PendingIntent} object isn't executed. - *
Note: Due to background execution limits introduced in Android - * {@link VERSION_CODES#O}, it's best to use a - * - * manifest-declared receiver to receive a callback. - *
Also, to prevent other apps from invoking the receiver, add the attribute assignment
- * android:exported="false" to the receiver's manifest entry.
- *ShortcutManager mShortcutManager =
- * context.getSystemService(ShortcutManager.class);
- *
- *if (mShortcutManager.isRequestPinShortcutSupported()) {
- *
- * // This example defines a new shortcut; that is, this shortcut hasn't
- * // been published before.
- * ShortcutInfo pinShortcutInfo = new ShortcutInfo.Builder()
- * .setIcon(myIcon)
- * .setShortLabel("My awesome shortcut")
- * .setIntent(myIntent)
- * .build();
- *
- * PendingIntent resultPendingIntent = null;
- *
- * // Create the following Intent and PendingIntent objects only if your app
- * // needs to be notified that the user allowed the shortcut to be pinned.
- * // Use a boolean value, such as "appNeedsNotifying", to define this behavior.
- * if (appNeedsNotifying) {
- * // We assume here the app has a manifest-declared receiver "MyReceiver".
- * Intent pinnedShortcutCallbackIntent = new Intent(context, MyReceiver.class);
- *
- * // Configure the intent so that your app's broadcast receiver gets
- * // the callback successfully.
- * PendingIntent successCallback = PendingIntent.createBroadcast(context, 0,
- * pinnedShortcutCallbackIntent);
- *
- * resultPendingIntent = successCallback.getIntentSender();
- * }
- *
- * mShortcutManager.requestPinShortcut(pinShortcutInfo, resultPendingIntent);
- *}
- *
- *
- * Note: As you add logic in your app to make requests to pin - * shortcuts, keep in mind that not all launchers support pinning of shortcuts. To determine whether - * your app can complete this process on a particular device, check the return value of - * {@link #isRequestPinShortcutSupported()}. Based on this return value, you might decide to hide - * the option in your app that allows users to pin a shortcut. - * - *
Note: See also the support library APIs - * {@link android.support.v4.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported( - * Context)} and - * {@link android.support.v4.content.pm.ShortcutManagerCompat#requestPinShortcut( - * Context, ShortcutInfoCompat, IntentSender)}, which works on Android versions lower than - * {@link VERSION_CODES#O} by falling back to the deprecated private intent - * {@code com.android.launcher.action.INSTALL_SHORTCUT}. - * - *
You can also create a specialized activity that helps users create shortcuts, complete with
- * custom options and a confirmation button. In your app's manifest file, add
- * {@link Intent#ACTION_CREATE_SHORTCUT} to the activity's <intent-filter>
- * element, as shown in the following snippet:
- *
- *
- *<manifest> - * ... - * <application> - * <activity android:name="com.example.MyCustomPromptToPinShortcut" ... > - * <intent-filter - * action android:name="android.intent.action.ACTION_CREATE_SHORTCUT"> - * ... - * </intent-filter> - * </activity> - * ... - * </application> - *</manifest> - *- * - *
When you use this specialized activity in your app, the following sequence of steps takes - * place:
+ *As an example, suppose {@link #getMaxShortcutCountPerActivity()} is 5: *
The {@link #addDynamicShortcuts(List)} and {@link #setDynamicShortcuts(List)} methods + * can also be used to update existing shortcuts with the same IDs, but they cannot be + * used for updating non-dynamic, pinned shortcuts because these 2 methods try to convert the + * given lists of shortcuts to dynamic shortcuts. *
* Dynamic shortcuts can be published with any set of {@link Intent#addFlags Intent} flags. * Typically, {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} is specified, possibly along with other * flags; otherwise, if the app is already running, the app is simply brought to * the foreground, and the target activity may not appear. * - *
The {@link ShortcutInfo.Builder#setIntents(Intent[])} method can be used instead of - * {@link ShortcutInfo.Builder#setIntent(Intent)} with {@link android.app.TaskStackBuilder} - * in order to launch an activity with other activities in the back stack. - * When the user selects a shortcut to load an activity with a back stack, - * then presses the back key, a parent activity from the same app will be shown - * instead of the user being navigated back to the launcher. - * - *
Static shortcuts can also have multiple intents to achieve the same effect.
- * In order to associate multiple {@link Intent} objects with a shortcut, simply list multiple
- * <intent> elements within a single <shortcut> element.
- * The last intent specifies what the user sees when they launch a shortcut.
- *
*
Static shortcuts cannot have custom intent flags.
* The first intent of a static shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK}
- * and {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set.
- * This means, when the app is already running, all the existing activities will be
- * destroyed when a static shortcut is launched.
- * If this behavior is not desirable, you can use a trampoline activity,
- * or an invisible activity that starts another activity in {@link Activity#onCreate},
- * then calls {@link Activity#finish()}.
- * The first activity should include an attribute setting
- * of {@code android:taskAffinity=""} in the app's AndroidManifest.xml
- * file, and the intent within the static shortcut should point at this first activity.
+ * and {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set. This means, when the app is already running, all
+ * the existing activities in your app will be destroyed when a static shortcut is launched.
+ * If this behavior is not desirable, you can use a trampoline activity, or an invisible
+ * activity that starts another activity in {@link Activity#onCreate}, then calls
+ * {@link Activity#finish()}:
+ *
AndroidManifest.xml file, the trampoline activity should include the
+ * attribute assignment {@code android:taskAffinity=""}.
+ * - * In order to avoid confusion, you should not use {@link #updateShortcuts(List)} to update - * a shortcut so that it contains conceptually different information. + *
Apps should update dynamic and pinned shortcuts when the system locale changes using the + * {@link Intent#ACTION_LOCALE_CHANGED} broadcast. When the system locale changes, + * rate limiting is reset, so even + * background apps can add and update dynamic shortcuts until the rate limit is reached again. * - *
For example, a phone app may publish the most frequently called contact as a dynamic - * shortcut. Over time, this contact may change. When it does, the app should - * represent the changed contact with a new shortcut that contains a different ID, using either - * {@link #setDynamicShortcuts(List)} or {@link #addDynamicShortcuts(List)}, rather than updating - * the existing shortcut with {@link #updateShortcuts(List)}. - * This is because when the shortcut is pinned, changing - * it to reference a different contact will likely confuse the user. + *
On the other hand, when the - * contact's information has changed, such as the name or picture, the app should - * use {@link #updateShortcuts(List)} so that the pinned shortcut is updated too. + *
Only main activities—activities that handle the {@code MAIN} action and the + * {@code LAUNCHER} category—can have shortcuts. If an app has multiple main activities, you + * need to define the set of shortcuts for each activity. * + *
Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of + * static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts that + * an app can create. * - *
Shortcut ranks are non-negative, sequential integers - * that determine the order in which shortcuts appear, assuming that the shortcuts are all in - * the same category. - * Ranks of existing shortcuts can be updated with - * {@link #updateShortcuts(List)}. You can also use {@link #addDynamicShortcuts(List)} and - * {@link #setDynamicShortcuts(List)}. + *
When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut, + * the pinned shortcut is still visible and launchable. This allows an app to have more than + * {@link #getMaxShortcutCountPerActivity()} number of shortcuts. * - *
Ranks are auto-adjusted so that they're unique for each target activity in each category - * (static or dynamic). For example, if there are 3 dynamic shortcuts with ranks 0, 1 and 2, - * adding another dynamic shortcut with a rank of 1 represents a request to place this shortcut at - * the second position. - * In response, the third and fourth shortcuts move closer to the bottom of the shortcut list, - * with their ranks changing to 2 and 3, respectively. + *
When rate limiting is active, + * {@link #isRateLimitingActive()} returns {@code true}. * - *
- * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcuts(List)}, and - * {@link #updateShortcuts(List)} may be rate-limited when called by background apps, or - * apps with no foreground activity or service. When you attempt to call these methods - * from a background app after exceeding the rate limit, these APIs return {@code false}. - * - *
Apps with a foreground activity or service are not rate-limited. - * - *
Rate-limiting is reset upon certain events, so that even background apps - * can call these APIs until the rate limit is reached again. - * These events include the following: + *
Rate limiting is reset upon certain events, so even background apps can call these APIs until + * the rate limit is reached again. These events include the following: *
When rate-limiting is active, {@link #isRateLimitingActive()} returns {@code true}. - * - *
- * If your app is rate-limited during development or testing, you can use the - * Reset ShortcutManager rate-limiting development option or - * the following {@code adb} command to reset it: - *
- *$ adb shell cmd shortcut reset-throttling [ --user USER-ID ] - *- * - *
- * Apps should update dynamic and pinned shortcuts when the system locale changes - * using the {@link Intent#ACTION_LOCALE_CHANGED} broadcast. - * - *
When the system locale changes, rate-limiting is reset, so even background apps - * can add and update dynamic shortcuts until the rate limit is reached again. - * - * - *
- * When an app has the {@code android:allowBackup="true"} attribute assignment included
- * in its AndroidManifest.xml file, pinned shortcuts are
- * backed up automatically and are restored when the user sets up a new device.
- *
- *
AndroidManifest.xml file.
- *
- * Because dynamic shortcuts are not restored, it is recommended that apps check - * currently-published dynamic shortcuts using {@link #getDynamicShortcuts()} - * each time they are launched, and they should re-publish - * dynamic shortcuts when necessary. - * - *
- *public class MainActivity extends Activity {
- * public void onCreate(Bundle savedInstanceState) {
- * super.onCreate(savedInstanceState);
- * ShortcutManager shortcutManager =
- * getSystemService(ShortcutManager.class);
- *
- * if (shortcutManager.getDynamicShortcuts().size() == 0) {
- * // Application restored. Need to re-publish dynamic shortcuts.
- * if (shortcutManager.getPinnedShortcuts().size() > 0) {
- * // Pinned shortcuts have been restored. Use
- * // updateShortcuts() to make sure they contain
- * // up-to-date information.
- * }
- * }
- * }
- * // ...
- *}
- *
- *
- *
- * - * Because pinned shortcuts are backed up and restored on new devices, shortcut IDs - * should contain either stable, constant strings or server-side identifiers, - * rather than identifiers generated locally that might not make sense on other devices. - * - * - *
- * Launcher apps may be capable of predicting which shortcuts will most likely be - * used at a given time by examining the shortcut usage history data. - * - *
In order to provide launchers with such data, publisher apps should - * report the shortcuts that are used with {@link #reportShortcutUsed(String)} - * when a shortcut is selected, - * or when an action equivalent to a shortcut is taken by the user even if it wasn't started - * with the shortcut. - * - *
For example, suppose a navigation app supports "navigate to work" as a shortcut. - * It should then report when the user selects this shortcut and when the user chooses - * to navigate to work within the app itself. - * This helps the launcher app - * learn that the user wants to navigate to work at a certain time every - * weekday, and it can then show this shortcut in a suggestion list at the right time. - * - *