• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.content.pm;
17 
18 import android.Manifest;
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SystemApi;
24 import android.annotation.SystemService;
25 import android.annotation.TestApi;
26 import android.annotation.UserIdInt;
27 import android.annotation.WorkerThread;
28 import android.app.Notification;
29 import android.app.usage.UsageStatsManager;
30 import android.compat.annotation.UnsupportedAppUsage;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.IntentSender;
36 import android.graphics.drawable.AdaptiveIconDrawable;
37 import android.os.Build;
38 import android.os.Build.VERSION_CODES;
39 import android.os.Parcel;
40 import android.os.Parcelable;
41 import android.os.RemoteException;
42 import android.os.ServiceManager;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.internal.infra.AndroidFuture;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.List;
50 import java.util.concurrent.ExecutionException;
51 
52 /**
53  * <p><code>ShortcutManager</code> executes operations on an app's set of <i>shortcuts</i>, which
54  * represent specific tasks and actions that users can perform within your app. This page lists
55  * components of the <code>ShortcutManager</code> class that you can use to create and manage
56  * sets of shortcuts.
57  *
58  * <p>To learn about methods that retrieve information about a single shortcut&mdash;including
59  * identifiers, type, and status&mdash;read the <code>
60  * <a href="/reference/android/content/pm/ShortcutInfo.html">ShortcutInfo</a></code> reference.
61  *
62  * <p>For guidance about using shortcuts, see
63  * <a href="/guide/topics/ui/shortcuts/index.html">App shortcuts</a>.
64  *
65  * <h3>Retrieving class instances</h3>
66  * <!-- Provides a heading for the content filled in by the @SystemService annotation below -->
67  */
68 @SystemService(Context.SHORTCUT_SERVICE)
69 public class ShortcutManager {
70     private static final String TAG = "ShortcutManager";
71 
72     /**
73      * Include manifest shortcuts in the result.
74      *
75      * @see #getShortcuts(int)
76      */
77     public static final int FLAG_MATCH_MANIFEST = 1 << 0;
78 
79     /**
80      * Include dynamic shortcuts in the result.
81      *
82      * @see #getShortcuts(int)
83      */
84     public static final int FLAG_MATCH_DYNAMIC = 1 << 1;
85 
86     /**
87      * Include pinned shortcuts in the result.
88      *
89      * @see #getShortcuts(int)
90      */
91     public static final int FLAG_MATCH_PINNED = 1 << 2;
92 
93     /**
94      * Include cached shortcuts in the result.
95      *
96      * @see #getShortcuts(int)
97      */
98     public static final int FLAG_MATCH_CACHED = 1 << 3;
99 
100     /** @hide */
101     @IntDef(flag = true, prefix = { "FLAG_MATCH_" }, value = {
102             FLAG_MATCH_MANIFEST,
103             FLAG_MATCH_DYNAMIC,
104             FLAG_MATCH_PINNED,
105             FLAG_MATCH_CACHED,
106     })
107     @Retention(RetentionPolicy.SOURCE)
108     public @interface ShortcutMatchFlags {}
109 
110     private final Context mContext;
111     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
112     private final IShortcutService mService;
113 
114     /**
115      * @hide
116      */
ShortcutManager(Context context, IShortcutService service)117     public ShortcutManager(Context context, IShortcutService service) {
118         mContext = context;
119         mService = service;
120     }
121 
122     /**
123      * @hide
124      */
125     @TestApi
ShortcutManager(Context context)126     public ShortcutManager(Context context) {
127         this(context, IShortcutService.Stub.asInterface(
128                 ServiceManager.getService(Context.SHORTCUT_SERVICE)));
129     }
130 
131     /**
132      * Publish the list of shortcuts.  All existing dynamic shortcuts from the caller app
133      * will be replaced.  If there are already pinned shortcuts with the same IDs,
134      * the mutable pinned shortcuts are updated.
135      *
136      * <p>This API will be rate-limited.
137      *
138      * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
139      *
140      * @throws IllegalArgumentException if {@link #getMaxShortcutCountPerActivity()} is exceeded,
141      * or when trying to update immutable shortcuts.
142      *
143      * @throws IllegalStateException when the user is locked.
144      */
145     @WorkerThread
setDynamicShortcuts(@onNull List<ShortcutInfo> shortcutInfoList)146     public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
147         try {
148             return mService.setDynamicShortcuts(mContext.getPackageName(), new ParceledListSlice(
149                     shortcutInfoList), injectMyUserId());
150         } catch (RemoteException e) {
151             throw e.rethrowFromSystemServer();
152         }
153     }
154 
155     /**
156      * Return all dynamic shortcuts from the caller app.
157      *
158      * <p>This API is intended to be used for examining what shortcuts are currently published.
159      * Re-publishing returned {@link ShortcutInfo}s via APIs such as
160      * {@link #setDynamicShortcuts(List)} may cause loss of information such as icons.
161      *
162      * @throws IllegalStateException when the user is locked.
163      */
164     @WorkerThread
165     @NonNull
getDynamicShortcuts()166     public List<ShortcutInfo> getDynamicShortcuts() {
167         try {
168             return mService.getShortcuts(mContext.getPackageName(),
169                     FLAG_MATCH_DYNAMIC, injectMyUserId()).getList();
170         } catch (RemoteException e) {
171             throw e.rethrowFromSystemServer();
172         }
173     }
174 
175     /**
176      * Return all static (manifest) shortcuts from the caller app.
177      *
178      * <p>This API is intended to be used for examining what shortcuts are currently published.
179      * Re-publishing returned {@link ShortcutInfo}s via APIs such as
180      * {@link #setDynamicShortcuts(List)} may cause loss of information such as icons.
181      *
182      * @throws IllegalStateException when the user is locked.
183      */
184     @WorkerThread
185     @NonNull
getManifestShortcuts()186     public List<ShortcutInfo> getManifestShortcuts() {
187         try {
188             return mService.getShortcuts(mContext.getPackageName(),
189                     FLAG_MATCH_MANIFEST, injectMyUserId()).getList();
190         } catch (RemoteException e) {
191             throw e.rethrowFromSystemServer();
192         }
193     }
194 
195     /**
196      * Returns {@link ShortcutInfo}s that match {@code matchFlags}.
197      *
198      * @param matchFlags result includes shortcuts matching this flags. Any combination of:
199      * <ul>
200      *     <li>{@link #FLAG_MATCH_MANIFEST}
201      *     <li>{@link #FLAG_MATCH_DYNAMIC}
202      *     <li>{@link #FLAG_MATCH_PINNED}
203      *     <li>{@link #FLAG_MATCH_CACHED}
204      * </ul>
205 
206      * @return list of {@link ShortcutInfo}s that match the flag.
207      *
208      * <p>At least one of the {@code MATCH} flags should be set. Otherwise no shortcuts will be
209      * returned.
210      *
211      * @throws IllegalStateException when the user is locked.
212      */
213     @WorkerThread
214     @NonNull
getShortcuts(@hortcutMatchFlags int matchFlags)215     public List<ShortcutInfo> getShortcuts(@ShortcutMatchFlags int matchFlags) {
216         try {
217             return mService.getShortcuts(mContext.getPackageName(), matchFlags,
218                     injectMyUserId()).getList();
219         } catch (RemoteException e) {
220             throw e.rethrowFromSystemServer();
221         }
222     }
223 
224     /**
225      * Publish the list of dynamic shortcuts.  If there are already dynamic or pinned shortcuts with
226      * the same IDs, each mutable shortcut is updated.
227      *
228      * <p>This API will be rate-limited.
229      *
230      * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
231      *
232      * @throws IllegalArgumentException if {@link #getMaxShortcutCountPerActivity()} is exceeded,
233      * or when trying to update immutable shortcuts.
234      *
235      * @throws IllegalStateException when the user is locked.
236      */
237     @WorkerThread
addDynamicShortcuts(@onNull List<ShortcutInfo> shortcutInfoList)238     public boolean addDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
239         try {
240             return mService.addDynamicShortcuts(mContext.getPackageName(),
241                     new ParceledListSlice(shortcutInfoList), injectMyUserId());
242         } catch (RemoteException e) {
243             throw e.rethrowFromSystemServer();
244         }
245     }
246 
247     /**
248      * Delete dynamic shortcuts by ID.
249      *
250      * @throws IllegalStateException when the user is locked.
251      */
removeDynamicShortcuts(@onNull List<String> shortcutIds)252     public void removeDynamicShortcuts(@NonNull List<String> shortcutIds) {
253         try {
254             mService.removeDynamicShortcuts(mContext.getPackageName(), shortcutIds,
255                     injectMyUserId());
256         } catch (RemoteException e) {
257             throw e.rethrowFromSystemServer();
258         }
259     }
260 
261     /**
262      * Delete all dynamic shortcuts from the caller app.
263      *
264      * @throws IllegalStateException when the user is locked.
265      */
removeAllDynamicShortcuts()266     public void removeAllDynamicShortcuts() {
267         try {
268             mService.removeAllDynamicShortcuts(mContext.getPackageName(), injectMyUserId());
269         } catch (RemoteException e) {
270             throw e.rethrowFromSystemServer();
271         }
272     }
273 
274     /**
275      * Delete long lived shortcuts by ID.
276      *
277      * @throws IllegalStateException when the user is locked.
278      */
removeLongLivedShortcuts(@onNull List<String> shortcutIds)279     public void removeLongLivedShortcuts(@NonNull List<String> shortcutIds) {
280         try {
281             mService.removeLongLivedShortcuts(mContext.getPackageName(), shortcutIds,
282                     injectMyUserId());
283         } catch (RemoteException e) {
284             throw e.rethrowFromSystemServer();
285         }
286     }
287 
288     /**
289      * Return all pinned shortcuts from the caller app.
290      *
291      * <p>This API is intended to be used for examining what shortcuts are currently published.
292      * Re-publishing returned {@link ShortcutInfo}s via APIs such as
293      * {@link #setDynamicShortcuts(List)} may cause loss of information such as icons.
294      *
295      * @throws IllegalStateException when the user is locked.
296      */
297     @WorkerThread
298     @NonNull
getPinnedShortcuts()299     public List<ShortcutInfo> getPinnedShortcuts() {
300         try {
301             return mService.getShortcuts(mContext.getPackageName(), FLAG_MATCH_PINNED,
302                     injectMyUserId()).getList();
303         } catch (RemoteException e) {
304             throw e.rethrowFromSystemServer();
305         }
306     }
307 
308     /**
309      * Update all existing shortcuts with the same IDs.  Target shortcuts may be pinned and/or
310      * dynamic, but they must not be immutable.
311      *
312      * <p>This API will be rate-limited.
313      *
314      * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
315      *
316      * @throws IllegalArgumentException If trying to update immutable shortcuts.
317      *
318      * @throws IllegalStateException when the user is locked.
319      */
320     @WorkerThread
updateShortcuts(@onNull List<ShortcutInfo> shortcutInfoList)321     public boolean updateShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
322         try {
323             return mService.updateShortcuts(mContext.getPackageName(),
324                     new ParceledListSlice(shortcutInfoList), injectMyUserId());
325         } catch (RemoteException e) {
326             throw e.rethrowFromSystemServer();
327         }
328     }
329 
330     /**
331      * Disable pinned shortcuts.  For more details, read
332      * <a href="/guide/topics/ui/shortcuts/managing-shortcuts.html#disable-shortcuts">
333      * Disable shortcuts</a>.
334      *
335      * @throws IllegalArgumentException If trying to disable immutable shortcuts.
336      *
337      * @throws IllegalStateException when the user is locked.
338      */
disableShortcuts(@onNull List<String> shortcutIds)339     public void disableShortcuts(@NonNull List<String> shortcutIds) {
340         try {
341             mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
342                     /* disabledMessage =*/ null, /* disabledMessageResId =*/ 0,
343                     injectMyUserId());
344         } catch (RemoteException e) {
345             throw e.rethrowFromSystemServer();
346         }
347     }
348 
349     /**
350      * @hide old signature, kept for unit testing.
351      */
disableShortcuts(@onNull List<String> shortcutIds, int disabledMessageResId)352     public void disableShortcuts(@NonNull List<String> shortcutIds, int disabledMessageResId) {
353         try {
354             mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
355                     /* disabledMessage =*/ null, disabledMessageResId,
356                     injectMyUserId());
357         } catch (RemoteException e) {
358             throw e.rethrowFromSystemServer();
359         }
360     }
361 
362     /**
363      * @hide old signature, kept for unit testing.
364      */
disableShortcuts(@onNull List<String> shortcutIds, String disabledMessage)365     public void disableShortcuts(@NonNull List<String> shortcutIds, String disabledMessage) {
366         disableShortcuts(shortcutIds, (CharSequence) disabledMessage);
367     }
368 
369     /**
370      * Disable pinned shortcuts, showing the user a custom error message when they try to select
371      * the disabled shortcuts.
372      * For more details, read
373      * <a href="/guide/topics/ui/shortcuts/managing-shortcuts.html#disable-shortcuts">
374      * Disable shortcuts</a>.
375      *
376      * @throws IllegalArgumentException If trying to disable immutable shortcuts.
377      *
378      * @throws IllegalStateException when the user is locked.
379      */
disableShortcuts(@onNull List<String> shortcutIds, CharSequence disabledMessage)380     public void disableShortcuts(@NonNull List<String> shortcutIds, CharSequence disabledMessage) {
381         try {
382             mService.disableShortcuts(mContext.getPackageName(), shortcutIds,
383                     disabledMessage, /* disabledMessageResId =*/ 0,
384                     injectMyUserId());
385         } catch (RemoteException e) {
386             throw e.rethrowFromSystemServer();
387         }
388     }
389 
390     /**
391      * Re-enable pinned shortcuts that were previously disabled.  If the target shortcuts
392      * are already enabled, this method does nothing.
393      *
394      * @throws IllegalArgumentException If trying to enable immutable shortcuts.
395      *
396      * @throws IllegalStateException when the user is locked.
397      */
enableShortcuts(@onNull List<String> shortcutIds)398     public void enableShortcuts(@NonNull List<String> shortcutIds) {
399         try {
400             mService.enableShortcuts(mContext.getPackageName(), shortcutIds, injectMyUserId());
401         } catch (RemoteException e) {
402             throw e.rethrowFromSystemServer();
403         }
404     }
405 
406 
407     /**
408      * @hide old signature, kept for unit testing.
409      */
getMaxShortcutCountForActivity()410     public int getMaxShortcutCountForActivity() {
411         return getMaxShortcutCountPerActivity();
412     }
413 
414     /**
415      * Return the maximum number of static and dynamic shortcuts that each launcher icon
416      * can have at a time.
417      */
getMaxShortcutCountPerActivity()418     public int getMaxShortcutCountPerActivity() {
419         try {
420             return mService.getMaxShortcutCountPerActivity(
421                     mContext.getPackageName(), injectMyUserId());
422         } catch (RemoteException e) {
423             throw e.rethrowFromSystemServer();
424         }
425     }
426 
427     /**
428      * Return the number of times the caller app can call the rate-limited APIs
429      * before the rate limit counter is reset.
430      *
431      * @see #getRateLimitResetTime()
432      *
433      * @hide
434      */
getRemainingCallCount()435     public int getRemainingCallCount() {
436         try {
437             return mService.getRemainingCallCount(mContext.getPackageName(), injectMyUserId());
438         } catch (RemoteException e) {
439             throw e.rethrowFromSystemServer();
440         }
441     }
442 
443     /**
444      * Return when the rate limit count will be reset next time, in milliseconds since the epoch.
445      *
446      * @see #getRemainingCallCount()
447      * @see System#currentTimeMillis()
448      *
449      * @hide
450      */
getRateLimitResetTime()451     public long getRateLimitResetTime() {
452         try {
453             return mService.getRateLimitResetTime(mContext.getPackageName(), injectMyUserId());
454         } catch (RemoteException e) {
455             throw e.rethrowFromSystemServer();
456         }
457     }
458 
459     /**
460      * Return {@code true} when rate-limiting is active for the caller app.
461      *
462      * <p>For details, see <a href="/guide/topics/ui/shortcuts/managing-shortcuts#rate-limiting">
463      * Rate limiting</a>.
464      *
465      * @throws IllegalStateException when the user is locked.
466      */
isRateLimitingActive()467     public boolean isRateLimitingActive() {
468         try {
469             return mService.getRemainingCallCount(mContext.getPackageName(), injectMyUserId())
470                     == 0;
471         } catch (RemoteException e) {
472             throw e.rethrowFromSystemServer();
473         }
474     }
475 
476     /**
477      * Return the max width for icons, in pixels.
478      *
479      * <p> Note that this method returns max width of icon's visible part. Hence, it does not take
480      * into account the inset introduced by {@link AdaptiveIconDrawable}. To calculate bitmap image
481      * to function as {@link AdaptiveIconDrawable}, multiply
482      * 1 + 2 * {@link AdaptiveIconDrawable#getExtraInsetFraction()} to the returned size.
483      */
getIconMaxWidth()484     public int getIconMaxWidth() {
485         try {
486             // TODO Implement it properly using xdpi.
487             return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
488         } catch (RemoteException e) {
489             throw e.rethrowFromSystemServer();
490         }
491     }
492 
493     /**
494      * Return the max height for icons, in pixels.
495      */
getIconMaxHeight()496     public int getIconMaxHeight() {
497         try {
498             // TODO Implement it properly using ydpi.
499             return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
500         } catch (RemoteException e) {
501             throw e.rethrowFromSystemServer();
502         }
503     }
504 
505     /**
506      * Apps that publish shortcuts should call this method whenever the user
507      * selects the shortcut containing the given ID or when the user completes
508      * an action in the app that is equivalent to selecting the shortcut.
509      * For more details, read about
510      * <a href="/guide/topics/ui/shortcuts/managing-shortcuts.html#track-usage">
511      * tracking shortcut usage</a>.
512      *
513      * <p>The information is accessible via {@link UsageStatsManager#queryEvents}
514      * Typically, launcher apps use this information to build a prediction model
515      * so that they can promote the shortcuts that are likely to be used at the moment.
516      *
517      * @throws IllegalStateException when the user is locked.
518      */
reportShortcutUsed(String shortcutId)519     public void reportShortcutUsed(String shortcutId) {
520         try {
521             mService.reportShortcutUsed(mContext.getPackageName(), shortcutId, injectMyUserId());
522         } catch (RemoteException e) {
523             throw e.rethrowFromSystemServer();
524         }
525     }
526 
527     /**
528      * Return {@code TRUE} if the app is running on a device whose default launcher supports
529      * {@link #requestPinShortcut(ShortcutInfo, IntentSender)}.
530      *
531      * <p>The return value may change in subsequent calls if the user changes the default launcher
532      * app.
533      *
534      * <p><b>Note:</b> See also the support library counterpart
535      * {@link androidx.core.content.pm.ShortcutManagerCompat#isRequestPinShortcutSupported(
536      * Context)}, which supports Android versions lower than {@link VERSION_CODES#O} using the
537      * legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}.
538      *
539      * @see #requestPinShortcut(ShortcutInfo, IntentSender)
540      */
isRequestPinShortcutSupported()541     public boolean isRequestPinShortcutSupported() {
542         try {
543             return mService.isRequestPinItemSupported(injectMyUserId(),
544                     LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT);
545         } catch (RemoteException e) {
546             throw e.rethrowFromSystemServer();
547         }
548     }
549 
550     /**
551      * Request to create a pinned shortcut.  The default launcher will receive this request and
552      * ask the user for approval.  If the user approves it, the shortcut will be created, and
553      * {@code resultIntent} will be sent. If a request is denied by the user, however, no response
554      * will be sent to the caller.
555      *
556      * <p>Only apps with a foreground activity or a foreground service can call this method.
557      * Otherwise, it'll throw {@link IllegalStateException}.
558      *
559      * <p>It's up to the launcher to decide how to handle previous pending requests when the same
560      * package calls this API multiple times in a row. One possible strategy is to ignore any
561      * previous requests.
562      *
563      * <p><b>Note:</b> See also the support library counterpart
564      * {@link androidx.core.content.pm.ShortcutManagerCompat#requestPinShortcut(
565      * Context, ShortcutInfoCompat, IntentSender)},
566      * which supports Android versions lower than {@link VERSION_CODES#O} using the
567      * legacy private intent {@code com.android.launcher.action.INSTALL_SHORTCUT}.
568      *
569      * @param shortcut Shortcut to pin.  If an app wants to pin an existing (either static
570      *     or dynamic) shortcut, then it only needs to have an ID. Although other fields don't have
571      *     to be set, the target shortcut must be enabled.
572      *
573      *     <p>If it's a new shortcut, all the mandatory fields, such as a short label, must be
574      *     set.
575      * @param resultIntent If not null, this intent will be sent when the shortcut is pinned.
576      *    Use {@link android.app.PendingIntent#getIntentSender()} to create an {@link IntentSender}.
577      *    To avoid background execution limits, use an unexported, manifest-declared receiver.
578      *    For more details, see
579      *    <a href="/guide/topics/ui/shortcuts/creating-shortcuts.html#pinned">
580      *    Creating pinned shortcuts</a>.
581      *
582      * @return {@code TRUE} if the launcher supports this feature.  Note the API will return without
583      *    waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean
584      *    the shortcut was pinned successfully.  {@code FALSE} if the launcher doesn't support this
585      *    feature.
586      *
587      * @see #isRequestPinShortcutSupported()
588      * @see IntentSender
589      * @see android.app.PendingIntent#getIntentSender()
590      *
591      * @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled.
592      * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
593      * service, or the device is locked.
594      */
595     @WorkerThread
requestPinShortcut(@onNull ShortcutInfo shortcut, @Nullable IntentSender resultIntent)596     public boolean requestPinShortcut(@NonNull ShortcutInfo shortcut,
597             @Nullable IntentSender resultIntent) {
598         try {
599             AndroidFuture<String> ret = new AndroidFuture<>();
600             mService.requestPinShortcut(mContext.getPackageName(), shortcut, resultIntent,
601                     injectMyUserId(), ret);
602             return Boolean.parseBoolean(getFutureOrThrow(ret));
603         } catch (RemoteException e) {
604             throw e.rethrowFromSystemServer();
605         }
606     }
607 
608     /**
609      * Returns an Intent which can be used by the default launcher to pin a shortcut containing the
610      * given {@link ShortcutInfo}. This method should be used by an Activity to set a result in
611      * response to {@link Intent#ACTION_CREATE_SHORTCUT}.
612      *
613      * @param shortcut New shortcut to pin.  If an app wants to pin an existing (either dynamic
614      *     or manifest) shortcut, then it only needs to have an ID, and other fields don't have to
615      *     be set, in which case, the target shortcut must be enabled.
616      *     If it's a new shortcut, all the mandatory fields, such as a short label, must be
617      *     set.
618      * @return The intent that should be set as the result for the calling activity, or
619      *     <code>null</code> if the current launcher doesn't support shortcuts.
620      *
621      * @see Intent#ACTION_CREATE_SHORTCUT
622      *
623      * @throws IllegalArgumentException if a shortcut with the same ID exists and is disabled.
624      */
625     @WorkerThread
createShortcutResultIntent(@onNull ShortcutInfo shortcut)626     public Intent createShortcutResultIntent(@NonNull ShortcutInfo shortcut) {
627         final AndroidFuture<Intent> ret = new AndroidFuture<>();
628         try {
629             mService.createShortcutResultIntent(mContext.getPackageName(),
630                     shortcut, injectMyUserId(), ret);
631             return getFutureOrThrow(ret);
632         } catch (RemoteException e) {
633             throw e.rethrowFromSystemServer();
634         }
635     }
636 
637     /**
638      * Called internally when an app is considered to have come to the foreground
639      * even when technically it's not.  This method resets the throttling for this package.
640      * For example, when the user sends an "inline reply" on a notification, the system UI will
641      * call it.
642      *
643      * @hide
644      */
onApplicationActive(@onNull String packageName, @UserIdInt int userId)645     public void onApplicationActive(@NonNull String packageName, @UserIdInt int userId) {
646         try {
647             mService.onApplicationActive(packageName, userId);
648         } catch (RemoteException e) {
649             throw e.rethrowFromSystemServer();
650         }
651     }
652 
653     /** @hide injection point */
654     @VisibleForTesting
injectMyUserId()655     protected int injectMyUserId() {
656         return mContext.getUserId();
657     }
658 
659     /**
660      * Used by framework's ShareSheet (ChooserActivity.java) to retrieve all of the direct share
661      * targets that match the given IntentFilter.
662      *
663      * @param filter IntentFilter that will be used to retrieve the matching {@link ShortcutInfo}s.
664      * @return List of {@link ShareShortcutInfo}s that match the given IntentFilter.
665      * @hide
666      */
667     @WorkerThread
668     @NonNull
669     @SystemApi
670     @RequiresPermission(Manifest.permission.MANAGE_APP_PREDICTIONS)
getShareTargets(@onNull IntentFilter filter)671     public List<ShareShortcutInfo> getShareTargets(@NonNull IntentFilter filter) {
672         try {
673             return mService.getShareTargets(
674                     mContext.getPackageName(), filter, injectMyUserId()).getList();
675         } catch (RemoteException e) {
676             throw e.rethrowFromSystemServer();
677         }
678     }
679 
680     /**
681      * Represents the result of a query return by {@link #getShareTargets(IntentFilter)}.
682      *
683      * @hide
684      */
685     @SystemApi
686     public static final class ShareShortcutInfo implements Parcelable {
687         private final ShortcutInfo mShortcutInfo;
688         private final ComponentName mTargetComponent;
689 
690         /**
691          * @hide
692          */
ShareShortcutInfo(@onNull ShortcutInfo shortcutInfo, @NonNull ComponentName targetComponent)693         public ShareShortcutInfo(@NonNull ShortcutInfo shortcutInfo,
694                 @NonNull ComponentName targetComponent) {
695             if (shortcutInfo == null) {
696                 throw new NullPointerException("shortcut info is null");
697             }
698             if (targetComponent == null) {
699                 throw new NullPointerException("target component is null");
700             }
701 
702             mShortcutInfo = shortcutInfo;
703             mTargetComponent = targetComponent;
704         }
705 
ShareShortcutInfo(@onNull Parcel in)706         private ShareShortcutInfo(@NonNull Parcel in) {
707             mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
708             mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
709         }
710 
711         @NonNull
getShortcutInfo()712         public ShortcutInfo getShortcutInfo() {
713             return mShortcutInfo;
714         }
715 
716         @NonNull
getTargetComponent()717         public ComponentName getTargetComponent() {
718             return mTargetComponent;
719         }
720 
721         @Override
describeContents()722         public int describeContents() {
723             return 0;
724         }
725 
726         @Override
writeToParcel(@onNull Parcel dest, int flags)727         public void writeToParcel(@NonNull Parcel dest, int flags) {
728             dest.writeParcelable(mShortcutInfo, flags);
729             dest.writeParcelable(mTargetComponent, flags);
730         }
731 
732         public static final @NonNull Parcelable.Creator<ShareShortcutInfo> CREATOR =
733                 new Parcelable.Creator<ShareShortcutInfo>() {
734                     public ShareShortcutInfo createFromParcel(Parcel in) {
735                         return new ShareShortcutInfo(in);
736                     }
737 
738                     public ShareShortcutInfo[] newArray(int size) {
739                         return new ShareShortcutInfo[size];
740                     }
741                 };
742     }
743 
744     /**
745      * Used by framework's ShareSheet (ChooserActivity.java) to check if a given package has share
746      * target definitions in it's resources.
747      *
748      * @param packageName Package to check for share targets.
749      * @return True if the package has any share target definitions, False otherwise.
750      * @hide
751      */
752     @SystemApi
hasShareTargets(@onNull String packageName)753     public boolean hasShareTargets(@NonNull String packageName) {
754         try {
755             return mService.hasShareTargets(mContext.getPackageName(), packageName,
756                     injectMyUserId());
757         } catch (RemoteException e) {
758             throw e.rethrowFromSystemServer();
759         }
760     }
761 
762     /**
763      * Publish a single dynamic shortcut. If there are already dynamic or pinned shortcuts with the
764      * same ID, each mutable shortcut is updated.
765      *
766      * <p>This method is useful when posting notifications which are tagged with shortcut IDs; In
767      * order to make sure shortcuts exist and are up-to-date, without the need to explicitly handle
768      * the shortcut count limit.
769      * @see android.app.NotificationManager#notify(int, Notification)
770      * @see android.app.Notification.Builder#setShortcutId(String)
771      *
772      * <p>If {@link #getMaxShortcutCountPerActivity()} is already reached, an existing shortcut with
773      * the lowest rank will be removed to add space for the new shortcut.
774      *
775      * <p>If the rank of the shortcut is not explicitly set, it will be set to zero, and shortcut
776      * will be added to the top of the list.
777      *
778      * @throws IllegalArgumentException if trying to update an immutable shortcut.
779      *
780      * @throws IllegalStateException when the user is locked.
781      */
pushDynamicShortcut(@onNull ShortcutInfo shortcut)782     public void pushDynamicShortcut(@NonNull ShortcutInfo shortcut) {
783         try {
784             mService.pushDynamicShortcut(mContext.getPackageName(), shortcut, injectMyUserId());
785         } catch (RemoteException e) {
786             throw e.rethrowFromSystemServer();
787         }
788     }
789 
getFutureOrThrow(@onNull AndroidFuture<T> future)790     private static <T> T getFutureOrThrow(@NonNull AndroidFuture<T> future) {
791         try {
792             return future.get();
793         } catch (Throwable e) {
794             if (e instanceof ExecutionException) {
795                 e = e.getCause();
796             }
797             if (e instanceof RuntimeException) {
798                 throw (RuntimeException) e;
799             }
800             if (e instanceof Error) {
801                 throw (Error) e;
802             }
803             throw new RuntimeException(e);
804         }
805     }
806 }
807