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