• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 
17 package android.content.pm;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.TestApi;
23 import android.content.ActivityNotFoundException;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager.ApplicationInfoFlags;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.res.Resources;
30 import android.graphics.Bitmap;
31 import android.graphics.BitmapFactory;
32 import android.graphics.Rect;
33 import android.graphics.drawable.BitmapDrawable;
34 import android.graphics.drawable.Drawable;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.Looper;
38 import android.os.Message;
39 import android.os.ParcelFileDescriptor;
40 import android.os.RemoteException;
41 import android.os.ServiceManager;
42 import android.os.UserHandle;
43 import android.os.UserManager;
44 import android.util.DisplayMetrics;
45 import android.util.Log;
46 
47 import java.io.IOException;
48 import java.lang.annotation.Retention;
49 import java.lang.annotation.RetentionPolicy;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.List;
54 
55 /**
56  * Class for retrieving a list of launchable activities for the current user and any associated
57  * managed profiles. This is mainly for use by launchers. Apps can be queried for each user profile.
58  * Since the PackageManager will not deliver package broadcasts for other profiles, you can register
59  * for package changes here.
60  * <p>
61  * To watch for managed profiles being added or removed, register for the following broadcasts:
62  * {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}.
63  * <p>
64  * You can retrieve the list of profiles associated with this user with
65  * {@link UserManager#getUserProfiles()}.
66  */
67 public class LauncherApps {
68 
69     static final String TAG = "LauncherApps";
70     static final boolean DEBUG = false;
71 
72     private Context mContext;
73     private ILauncherApps mService;
74     private PackageManager mPm;
75 
76     private List<CallbackMessageHandler> mCallbacks
77             = new ArrayList<CallbackMessageHandler>();
78 
79     /**
80      * Callbacks for package changes to this and related managed profiles.
81      */
82     public static abstract class Callback {
83         /**
84          * Indicates that a package was removed from the specified profile.
85          *
86          * If a package is removed while being updated onPackageChanged will be
87          * called instead.
88          *
89          * @param packageName The name of the package that was removed.
90          * @param user The UserHandle of the profile that generated the change.
91          */
onPackageRemoved(String packageName, UserHandle user)92         abstract public void onPackageRemoved(String packageName, UserHandle user);
93 
94         /**
95          * Indicates that a package was added to the specified profile.
96          *
97          * If a package is added while being updated then onPackageChanged will be
98          * called instead.
99          *
100          * @param packageName The name of the package that was added.
101          * @param user The UserHandle of the profile that generated the change.
102          */
onPackageAdded(String packageName, UserHandle user)103         abstract public void onPackageAdded(String packageName, UserHandle user);
104 
105         /**
106          * Indicates that a package was modified in the specified profile.
107          * This can happen, for example, when the package is updated or when
108          * one or more components are enabled or disabled.
109          *
110          * @param packageName The name of the package that has changed.
111          * @param user The UserHandle of the profile that generated the change.
112          */
onPackageChanged(String packageName, UserHandle user)113         abstract public void onPackageChanged(String packageName, UserHandle user);
114 
115         /**
116          * Indicates that one or more packages have become available. For
117          * example, this can happen when a removable storage card has
118          * reappeared.
119          *
120          * @param packageNames The names of the packages that have become
121          *            available.
122          * @param user The UserHandle of the profile that generated the change.
123          * @param replacing Indicates whether these packages are replacing
124          *            existing ones.
125          */
onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing)126         abstract public void onPackagesAvailable(String[] packageNames, UserHandle user,
127                 boolean replacing);
128 
129         /**
130          * Indicates that one or more packages have become unavailable. For
131          * example, this can happen when a removable storage card has been
132          * removed.
133          *
134          * @param packageNames The names of the packages that have become
135          *            unavailable.
136          * @param user The UserHandle of the profile that generated the change.
137          * @param replacing Indicates whether the packages are about to be
138          *            replaced with new versions.
139          */
onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing)140         abstract public void onPackagesUnavailable(String[] packageNames, UserHandle user,
141                 boolean replacing);
142 
143         /**
144          * Indicates that one or more packages have been suspended. For
145          * example, this can happen when a Device Administrator suspends
146          * an applicaton.
147          *
148          * @param packageNames The names of the packages that have just been
149          *            suspended.
150          * @param user The UserHandle of the profile that generated the change.
151          */
onPackagesSuspended(String[] packageNames, UserHandle user)152         public void onPackagesSuspended(String[] packageNames, UserHandle user) {
153         }
154 
155         /**
156          * Indicates that one or more packages have been unsuspended. For
157          * example, this can happen when a Device Administrator unsuspends
158          * an applicaton.
159          *
160          * @param packageNames The names of the packages that have just been
161          *            unsuspended.
162          * @param user The UserHandle of the profile that generated the change.
163          */
onPackagesUnsuspended(String[] packageNames, UserHandle user)164         public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
165         }
166 
167         /**
168          * Indicates that one or more shortcuts of any kind (dynamic, pinned, or manifest)
169          * have been added, updated or removed.
170          *
171          * <p>Only the applications that are allowed to access the shortcut information,
172          * as defined in {@link #hasShortcutHostPermission()}, will receive it.
173          *
174          * @param packageName The name of the package that has the shortcuts.
175          * @param shortcuts All shortcuts from the package (dynamic, manifest and/or pinned).
176          *    Only "key" information will be provided, as defined in
177          *    {@link ShortcutInfo#hasKeyFieldsOnly()}.
178          * @param user The UserHandle of the profile that generated the change.
179          *
180          * @see ShortcutManager
181          */
onShortcutsChanged(@onNull String packageName, @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user)182         public void onShortcutsChanged(@NonNull String packageName,
183                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
184         }
185     }
186 
187     /**
188      * Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}.
189      */
190     public static class ShortcutQuery {
191         /**
192          * Include dynamic shortcuts in the result.
193          */
194         public static final int FLAG_MATCH_DYNAMIC = 1 << 0;
195 
196         /** @hide kept for unit tests */
197         @Deprecated
198         public static final int FLAG_GET_DYNAMIC = FLAG_MATCH_DYNAMIC;
199 
200         /**
201          * Include pinned shortcuts in the result.
202          */
203         public static final int FLAG_MATCH_PINNED = 1 << 1;
204 
205         /** @hide kept for unit tests */
206         @Deprecated
207         public static final int FLAG_GET_PINNED = FLAG_MATCH_PINNED;
208 
209         /**
210          * Include manifest shortcuts in the result.
211          */
212         public static final int FLAG_MATCH_MANIFEST = 1 << 3;
213 
214         /** @hide kept for unit tests */
215         @Deprecated
216         public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
217 
218         /** @hide */
219         public static final int FLAG_MATCH_ALL_KINDS =
220                 FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
221 
222         /** @hide kept for unit tests */
223         @Deprecated
224         public static final int FLAG_GET_ALL_KINDS = FLAG_MATCH_ALL_KINDS;
225 
226         /**
227          * Requests "key" fields only.  See {@link ShortcutInfo#hasKeyFieldsOnly()}'s javadoc to
228          * see which fields fields "key".
229          * This allows quicker access to shortcut information in order to
230          * determine whether the caller's in-memory cache needs to be updated.
231          *
232          * <p>Typically, launcher applications cache all or most shortcut information
233          * in memory in order to show shortcuts without a delay.
234          *
235          * When a given launcher application wants to update its cache, such as when its process
236          * restarts, it can fetch shortcut information with this flag.
237          * The application can then check {@link ShortcutInfo#getLastChangedTimestamp()} for each
238          * shortcut, fetching a shortcut's non-key information only if that shortcut has been
239          * updated.
240          *
241          * @see ShortcutManager
242          */
243         public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2;
244 
245         /** @hide */
246         @IntDef(flag = true,
247                 value = {
248                         FLAG_MATCH_DYNAMIC,
249                         FLAG_MATCH_PINNED,
250                         FLAG_MATCH_MANIFEST,
251                         FLAG_GET_KEY_FIELDS_ONLY,
252                 })
253         @Retention(RetentionPolicy.SOURCE)
254         public @interface QueryFlags {}
255 
256         long mChangedSince;
257 
258         @Nullable
259         String mPackage;
260 
261         @Nullable
262         List<String> mShortcutIds;
263 
264         @Nullable
265         ComponentName mActivity;
266 
267         @QueryFlags
268         int mQueryFlags;
269 
ShortcutQuery()270         public ShortcutQuery() {
271         }
272 
273         /**
274          * If non-zero, returns only shortcuts that have been added or updated
275          * since the given timestamp, expressed in milliseconds since the Epoch&mdash;see
276          * {@link System#currentTimeMillis()}.
277          */
setChangedSince(long changedSince)278         public ShortcutQuery setChangedSince(long changedSince) {
279             mChangedSince = changedSince;
280             return this;
281         }
282 
283         /**
284          * If non-null, returns only shortcuts from the package.
285          */
setPackage(@ullable String packageName)286         public ShortcutQuery setPackage(@Nullable String packageName) {
287             mPackage = packageName;
288             return this;
289         }
290 
291         /**
292          * If non-null, return only the specified shortcuts by ID.  When setting this field,
293          * a package name must also be set with {@link #setPackage}.
294          */
setShortcutIds(@ullable List<String> shortcutIds)295         public ShortcutQuery setShortcutIds(@Nullable List<String> shortcutIds) {
296             mShortcutIds = shortcutIds;
297             return this;
298         }
299 
300         /**
301          * If non-null, returns only shortcuts associated with the activity; i.e.
302          * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
303          * to {@code activity}.
304          */
setActivity(@ullable ComponentName activity)305         public ShortcutQuery setActivity(@Nullable ComponentName activity) {
306             mActivity = activity;
307             return this;
308         }
309 
310         /**
311          * Set query options.  At least one of the {@code MATCH} flags should be set.  Otherwise,
312          * no shortcuts will be returned.
313          *
314          * <ul>
315          *     <li>{@link #FLAG_MATCH_DYNAMIC}
316          *     <li>{@link #FLAG_MATCH_PINNED}
317          *     <li>{@link #FLAG_MATCH_MANIFEST}
318          *     <li>{@link #FLAG_GET_KEY_FIELDS_ONLY}
319          * </ul>
320          */
setQueryFlags(@ueryFlags int queryFlags)321         public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
322             mQueryFlags = queryFlags;
323             return this;
324         }
325     }
326 
327     /** @hide */
LauncherApps(Context context, ILauncherApps service)328     public LauncherApps(Context context, ILauncherApps service) {
329         mContext = context;
330         mService = service;
331         mPm = context.getPackageManager();
332     }
333 
334     /** @hide */
335     @TestApi
LauncherApps(Context context)336     public LauncherApps(Context context) {
337         this(context, ILauncherApps.Stub.asInterface(
338                 ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE)));
339     }
340 
341     /**
342      * Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and
343      * {@link Intent#CATEGORY_LAUNCHER}, for a specified user.
344      *
345      * @param packageName The specific package to query. If null, it checks all installed packages
346      *            in the profile.
347      * @param user The UserHandle of the profile.
348      * @return List of launchable activities. Can be an empty list but will not be null.
349      */
getActivityList(String packageName, UserHandle user)350     public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
351         ParceledListSlice<ResolveInfo> activities = null;
352         try {
353             activities = mService.getLauncherActivities(packageName, user);
354         } catch (RemoteException re) {
355             throw re.rethrowFromSystemServer();
356         }
357         if (activities == null) {
358             return Collections.EMPTY_LIST;
359         }
360         ArrayList<LauncherActivityInfo> lais = new ArrayList<LauncherActivityInfo>();
361         for (ResolveInfo ri : activities.getList()) {
362             LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri.activityInfo, user);
363             if (DEBUG) {
364                 Log.v(TAG, "Returning activity for profile " + user + " : "
365                         + lai.getComponentName());
366             }
367             lais.add(lai);
368         }
369         return lais;
370     }
371 
372     /**
373      * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
374      * returns null.
375      *
376      * @param intent The intent to find a match for.
377      * @param user The profile to look in for a match.
378      * @return An activity info object if there is a match.
379      */
resolveActivity(Intent intent, UserHandle user)380     public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
381         try {
382             ActivityInfo ai = mService.resolveActivity(intent.getComponent(), user);
383             if (ai != null) {
384                 LauncherActivityInfo info = new LauncherActivityInfo(mContext, ai, user);
385                 return info;
386             }
387         } catch (RemoteException re) {
388             throw re.rethrowFromSystemServer();
389         }
390         return null;
391     }
392 
393     /**
394      * Starts a Main activity in the specified profile.
395      *
396      * @param component The ComponentName of the activity to launch
397      * @param user The UserHandle of the profile
398      * @param sourceBounds The Rect containing the source bounds of the clicked icon
399      * @param opts Options to pass to startActivity
400      */
startMainActivity(ComponentName component, UserHandle user, Rect sourceBounds, Bundle opts)401     public void startMainActivity(ComponentName component, UserHandle user, Rect sourceBounds,
402             Bundle opts) {
403         if (DEBUG) {
404             Log.i(TAG, "StartMainActivity " + component + " " + user.getIdentifier());
405         }
406         try {
407             mService.startActivityAsUser(component, sourceBounds, opts, user);
408         } catch (RemoteException re) {
409             throw re.rethrowFromSystemServer();
410         }
411     }
412 
413     /**
414      * Starts the settings activity to show the application details for a
415      * package in the specified profile.
416      *
417      * @param component The ComponentName of the package to launch settings for.
418      * @param user The UserHandle of the profile
419      * @param sourceBounds The Rect containing the source bounds of the clicked icon
420      * @param opts Options to pass to startActivity
421      */
startAppDetailsActivity(ComponentName component, UserHandle user, Rect sourceBounds, Bundle opts)422     public void startAppDetailsActivity(ComponentName component, UserHandle user,
423             Rect sourceBounds, Bundle opts) {
424         try {
425             mService.showAppDetailsAsUser(component, sourceBounds, opts, user);
426         } catch (RemoteException re) {
427             throw re.rethrowFromSystemServer();
428         }
429     }
430 
431     /**
432      * Checks if the package is installed and enabled for a profile.
433      *
434      * @param packageName The package to check.
435      * @param user The UserHandle of the profile.
436      *
437      * @return true if the package exists and is enabled.
438      */
isPackageEnabled(String packageName, UserHandle user)439     public boolean isPackageEnabled(String packageName, UserHandle user) {
440         try {
441             return mService.isPackageEnabled(packageName, user);
442         } catch (RemoteException re) {
443             throw re.rethrowFromSystemServer();
444         }
445     }
446 
447     /**
448      * Retrieve all of the information we know about a particular package / application.
449      *
450      * @param packageName The package of the application
451      * @param flags Additional option flags {@link PackageManager#getApplicationInfo}
452      * @param user The UserHandle of the profile.
453      *
454      * @return An {@link ApplicationInfo} containing information about the package or
455      *         null of the package isn't found.
456      * @hide
457      */
getApplicationInfo(String packageName, @ApplicationInfoFlags int flags, UserHandle user)458     public ApplicationInfo getApplicationInfo(String packageName, @ApplicationInfoFlags int flags,
459             UserHandle user) {
460         try {
461             return mService.getApplicationInfo(packageName, flags, user);
462         } catch (RemoteException re) {
463             throw re.rethrowFromSystemServer();
464         }
465     }
466 
467     /**
468      * Checks if the activity exists and it enabled for a profile.
469      *
470      * @param component The activity to check.
471      * @param user The UserHandle of the profile.
472      *
473      * @return true if the activity exists and is enabled.
474      */
isActivityEnabled(ComponentName component, UserHandle user)475     public boolean isActivityEnabled(ComponentName component, UserHandle user) {
476         try {
477             return mService.isActivityEnabled(component, user);
478         } catch (RemoteException re) {
479             throw re.rethrowFromSystemServer();
480         }
481     }
482 
483     /**
484      * Returns whether the caller can access the shortcut information.
485      *
486      * <p>Only the default launcher can access the shortcut information.
487      *
488      * <p>Note when this method returns {@code false}, it may be a temporary situation because
489      * the user is trying a new launcher application.  The user may decide to change the default
490      * launcher back to the calling application again, so even if a launcher application loses
491      * this permission, it does <b>not</b> have to purge pinned shortcut information.
492      * If the calling launcher application contains pinned shortcuts, they will still work,
493      * even though the caller no longer has the shortcut host permission.
494      *
495      * @throws IllegalStateException when the user is locked.
496      *
497      * @see ShortcutManager
498      */
hasShortcutHostPermission()499     public boolean hasShortcutHostPermission() {
500         try {
501             return mService.hasShortcutHostPermission(mContext.getPackageName());
502         } catch (RemoteException re) {
503             throw re.rethrowFromSystemServer();
504         }
505     }
506 
507     /**
508      * Returns {@link ShortcutInfo}s that match {@code query}.
509      *
510      * <p>Callers must be allowed to access the shortcut information, as defined in {@link
511      * #hasShortcutHostPermission()}.
512      *
513      * @param query result includes shortcuts matching this query.
514      * @param user The UserHandle of the profile.
515      *
516      * @return the IDs of {@link ShortcutInfo}s that match the query.
517      * @throws IllegalStateException when the user is locked, or when the {@code user} user
518      * is locked or not running.
519      *
520      * @see ShortcutManager
521      */
522     @Nullable
getShortcuts(@onNull ShortcutQuery query, @NonNull UserHandle user)523     public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query,
524             @NonNull UserHandle user) {
525         try {
526             return mService.getShortcuts(mContext.getPackageName(),
527                     query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
528                     query.mQueryFlags, user)
529                     .getList();
530         } catch (RemoteException e) {
531             throw e.rethrowFromSystemServer();
532         }
533     }
534 
535     /**
536      * @hide // No longer used.  Use getShortcuts() instead.  Kept for unit tests.
537      */
538     @Nullable
539     @Deprecated
getShortcutInfo(@onNull String packageName, @NonNull List<String> ids, @NonNull UserHandle user)540     public List<ShortcutInfo> getShortcutInfo(@NonNull String packageName,
541             @NonNull List<String> ids, @NonNull UserHandle user) {
542         final ShortcutQuery q = new ShortcutQuery();
543         q.setPackage(packageName);
544         q.setShortcutIds(ids);
545         q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
546         return getShortcuts(q, user);
547     }
548 
549     /**
550      * Pin shortcuts on a package.
551      *
552      * <p>This API is <b>NOT</b> cumulative; this will replace all pinned shortcuts for the package.
553      * However, different launchers may have different set of pinned shortcuts.
554      *
555      * <p>The calling launcher application must be allowed to access the shortcut information,
556      * as defined in {@link #hasShortcutHostPermission()}.
557      *
558      * @param packageName The target package name.
559      * @param shortcutIds The IDs of the shortcut to be pinned.
560      * @param user The UserHandle of the profile.
561      * @throws IllegalStateException when the user is locked, or when the {@code user} user
562      * is locked or not running.
563      *
564      * @see ShortcutManager
565      */
pinShortcuts(@onNull String packageName, @NonNull List<String> shortcutIds, @NonNull UserHandle user)566     public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
567             @NonNull UserHandle user) {
568         try {
569             mService.pinShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
570         } catch (RemoteException e) {
571             throw e.rethrowFromSystemServer();
572         }
573     }
574 
575     /**
576      * @hide kept for testing.
577      */
578     @Deprecated
getShortcutIconResId(@onNull ShortcutInfo shortcut)579     public int getShortcutIconResId(@NonNull ShortcutInfo shortcut) {
580         return shortcut.getIconResourceId();
581     }
582 
583     /**
584      * @hide kept for testing.
585      */
586     @Deprecated
getShortcutIconResId(@onNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user)587     public int getShortcutIconResId(@NonNull String packageName, @NonNull String shortcutId,
588             @NonNull UserHandle user) {
589         final ShortcutQuery q = new ShortcutQuery();
590         q.setPackage(packageName);
591         q.setShortcutIds(Arrays.asList(shortcutId));
592         q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
593         final List<ShortcutInfo> shortcuts = getShortcuts(q, user);
594 
595         return shortcuts.size() > 0 ? shortcuts.get(0).getIconResourceId() : 0;
596     }
597 
598     /**
599      * @hide internal/unit tests only
600      */
getShortcutIconFd( @onNull ShortcutInfo shortcut)601     public ParcelFileDescriptor getShortcutIconFd(
602             @NonNull ShortcutInfo shortcut) {
603         return getShortcutIconFd(shortcut.getPackage(), shortcut.getId(),
604                 shortcut.getUserId());
605     }
606 
607     /**
608      * @hide internal/unit tests only
609      */
getShortcutIconFd( @onNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user)610     public ParcelFileDescriptor getShortcutIconFd(
611             @NonNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user) {
612         return getShortcutIconFd(packageName, shortcutId, user.getIdentifier());
613     }
614 
getShortcutIconFd( @onNull String packageName, @NonNull String shortcutId, int userId)615     private ParcelFileDescriptor getShortcutIconFd(
616             @NonNull String packageName, @NonNull String shortcutId, int userId) {
617         try {
618             return mService.getShortcutIconFd(mContext.getPackageName(),
619                     packageName, shortcutId, userId);
620         } catch (RemoteException e) {
621             throw e.rethrowFromSystemServer();
622         }
623     }
624 
625     /**
626      * Returns the icon for this shortcut, without any badging for the profile.
627      *
628      * <p>The calling launcher application must be allowed to access the shortcut information,
629      * as defined in {@link #hasShortcutHostPermission()}.
630      *
631      * @param density The preferred density of the icon, zero for default density. Use
632      * density DPI values from {@link DisplayMetrics}.
633      *
634      * @return The drawable associated with the shortcut.
635      * @throws IllegalStateException when the user is locked, or when the {@code user} user
636      * is locked or not running.
637      *
638      * @see ShortcutManager
639      * @see #getShortcutBadgedIconDrawable(ShortcutInfo, int)
640      * @see DisplayMetrics
641      */
getShortcutIconDrawable(@onNull ShortcutInfo shortcut, int density)642     public Drawable getShortcutIconDrawable(@NonNull ShortcutInfo shortcut, int density) {
643         if (shortcut.hasIconFile()) {
644             final ParcelFileDescriptor pfd = getShortcutIconFd(shortcut);
645             if (pfd == null) {
646                 return null;
647             }
648             try {
649                 final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
650                 return (bmp == null) ? null : new BitmapDrawable(mContext.getResources(), bmp);
651             } finally {
652                 try {
653                     pfd.close();
654                 } catch (IOException ignore) {
655                 }
656             }
657         } else if (shortcut.hasIconResource()) {
658             try {
659                 final int resId = shortcut.getIconResourceId();
660                 if (resId == 0) {
661                     return null; // Shouldn't happen but just in case.
662                 }
663                 final ApplicationInfo ai = getApplicationInfo(shortcut.getPackage(),
664                         /* flags =*/ 0, shortcut.getUserHandle());
665                 final Resources res = mContext.getPackageManager().getResourcesForApplication(ai);
666                 return res.getDrawableForDensity(resId, density);
667             } catch (NameNotFoundException | Resources.NotFoundException e) {
668                 return null;
669             }
670         } else {
671             return null; // Has no icon.
672         }
673     }
674 
675     /**
676      * Returns the shortcut icon with badging appropriate for the profile.
677      *
678      * <p>The calling launcher application must be allowed to access the shortcut information,
679      * as defined in {@link #hasShortcutHostPermission()}.
680      *
681      * @param density Optional density for the icon, or 0 to use the default density. Use
682      * @return A badged icon for the shortcut.
683      * @throws IllegalStateException when the user is locked, or when the {@code user} user
684      * is locked or not running.
685      *
686      * @see ShortcutManager
687      * @see #getShortcutIconDrawable(ShortcutInfo, int)
688      * @see DisplayMetrics
689      */
getShortcutBadgedIconDrawable(ShortcutInfo shortcut, int density)690     public Drawable getShortcutBadgedIconDrawable(ShortcutInfo shortcut, int density) {
691         final Drawable originalIcon = getShortcutIconDrawable(shortcut, density);
692 
693         return (originalIcon == null) ? null : mContext.getPackageManager().getUserBadgedIcon(
694                 originalIcon, shortcut.getUserHandle());
695     }
696 
697     /**
698      * Starts a shortcut.
699      *
700      * <p>The calling launcher application must be allowed to access the shortcut information,
701      * as defined in {@link #hasShortcutHostPermission()}.
702      *
703      * @param packageName The target shortcut package name.
704      * @param shortcutId The target shortcut ID.
705      * @param sourceBounds The Rect containing the source bounds of the clicked icon.
706      * @param startActivityOptions Options to pass to startActivity.
707      * @param user The UserHandle of the profile.
708      * @throws IllegalStateException when the user is locked, or when the {@code user} user
709      * is locked or not running.
710      *
711      * @throws android.content.ActivityNotFoundException failed to start shortcut. (e.g.
712      * the shortcut no longer exists, is disabled, the intent receiver activity doesn't exist, etc)
713      */
startShortcut(@onNull String packageName, @NonNull String shortcutId, @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions, @NonNull UserHandle user)714     public void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
715             @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
716             @NonNull UserHandle user) {
717         startShortcut(packageName, shortcutId, sourceBounds, startActivityOptions,
718                 user.getIdentifier());
719     }
720 
721     /**
722      * Launches a shortcut.
723      *
724      * <p>The calling launcher application must be allowed to access the shortcut information,
725      * as defined in {@link #hasShortcutHostPermission()}.
726      *
727      * @param shortcut The target shortcut.
728      * @param sourceBounds The Rect containing the source bounds of the clicked icon.
729      * @param startActivityOptions Options to pass to startActivity.
730      * @throws IllegalStateException when the user is locked, or when the {@code user} user
731      * is locked or not running.
732      *
733      * @throws android.content.ActivityNotFoundException failed to start shortcut. (e.g.
734      * the shortcut no longer exists, is disabled, the intent receiver activity doesn't exist, etc)
735      */
startShortcut(@onNull ShortcutInfo shortcut, @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions)736     public void startShortcut(@NonNull ShortcutInfo shortcut,
737             @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
738         startShortcut(shortcut.getPackage(), shortcut.getId(),
739                 sourceBounds, startActivityOptions,
740                 shortcut.getUserId());
741     }
742 
startShortcut(@onNull String packageName, @NonNull String shortcutId, @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions, int userId)743     private void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
744             @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
745             int userId) {
746         try {
747             final boolean success =
748                     mService.startShortcut(mContext.getPackageName(), packageName, shortcutId,
749                     sourceBounds, startActivityOptions, userId);
750             if (!success) {
751                 throw new ActivityNotFoundException("Shortcut could not be started");
752             }
753         } catch (RemoteException e) {
754             throw e.rethrowFromSystemServer();
755         }
756     }
757 
758     /**
759      * Registers a callback for changes to packages in current and managed profiles.
760      *
761      * @param callback The callback to register.
762      */
registerCallback(Callback callback)763     public void registerCallback(Callback callback) {
764         registerCallback(callback, null);
765     }
766 
767     /**
768      * Registers a callback for changes to packages in current and managed profiles.
769      *
770      * @param callback The callback to register.
771      * @param handler that should be used to post callbacks on, may be null.
772      */
registerCallback(Callback callback, Handler handler)773     public void registerCallback(Callback callback, Handler handler) {
774         synchronized (this) {
775             if (callback != null && findCallbackLocked(callback) < 0) {
776                 boolean addedFirstCallback = mCallbacks.size() == 0;
777                 addCallbackLocked(callback, handler);
778                 if (addedFirstCallback) {
779                     try {
780                         mService.addOnAppsChangedListener(mContext.getPackageName(),
781                                 mAppsChangedListener);
782                     } catch (RemoteException re) {
783                         throw re.rethrowFromSystemServer();
784                     }
785                 }
786             }
787         }
788     }
789 
790     /**
791      * Unregisters a callback that was previously registered.
792      *
793      * @param callback The callback to unregister.
794      * @see #registerCallback(Callback)
795      */
unregisterCallback(Callback callback)796     public void unregisterCallback(Callback callback) {
797         synchronized (this) {
798             removeCallbackLocked(callback);
799             if (mCallbacks.size() == 0) {
800                 try {
801                     mService.removeOnAppsChangedListener(mAppsChangedListener);
802                 } catch (RemoteException re) {
803                     throw re.rethrowFromSystemServer();
804                 }
805             }
806         }
807     }
808 
809     /** @return position in mCallbacks for callback or -1 if not present. */
findCallbackLocked(Callback callback)810     private int findCallbackLocked(Callback callback) {
811         if (callback == null) {
812             throw new IllegalArgumentException("Callback cannot be null");
813         }
814         final int size = mCallbacks.size();
815         for (int i = 0; i < size; ++i) {
816             if (mCallbacks.get(i).mCallback == callback) {
817                 return i;
818             }
819         }
820         return -1;
821     }
822 
removeCallbackLocked(Callback callback)823     private void removeCallbackLocked(Callback callback) {
824         int pos = findCallbackLocked(callback);
825         if (pos >= 0) {
826             mCallbacks.remove(pos);
827         }
828     }
829 
addCallbackLocked(Callback callback, Handler handler)830     private void addCallbackLocked(Callback callback, Handler handler) {
831         // Remove if already present.
832         removeCallbackLocked(callback);
833         if (handler == null) {
834             handler = new Handler();
835         }
836         CallbackMessageHandler toAdd = new CallbackMessageHandler(handler.getLooper(), callback);
837         mCallbacks.add(toAdd);
838     }
839 
840     private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {
841 
842         @Override
843         public void onPackageRemoved(UserHandle user, String packageName)
844                 throws RemoteException {
845             if (DEBUG) {
846                 Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName);
847             }
848             synchronized (LauncherApps.this) {
849                 for (CallbackMessageHandler callback : mCallbacks) {
850                     callback.postOnPackageRemoved(packageName, user);
851                 }
852             }
853         }
854 
855         @Override
856         public void onPackageChanged(UserHandle user, String packageName) throws RemoteException {
857             if (DEBUG) {
858                 Log.d(TAG, "onPackageChanged " + user.getIdentifier() + "," + packageName);
859             }
860             synchronized (LauncherApps.this) {
861                 for (CallbackMessageHandler callback : mCallbacks) {
862                     callback.postOnPackageChanged(packageName, user);
863                 }
864             }
865         }
866 
867         @Override
868         public void onPackageAdded(UserHandle user, String packageName) throws RemoteException {
869             if (DEBUG) {
870                 Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName);
871             }
872             synchronized (LauncherApps.this) {
873                 for (CallbackMessageHandler callback : mCallbacks) {
874                     callback.postOnPackageAdded(packageName, user);
875                 }
876             }
877         }
878 
879         @Override
880         public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing)
881                 throws RemoteException {
882             if (DEBUG) {
883                 Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames);
884             }
885             synchronized (LauncherApps.this) {
886                 for (CallbackMessageHandler callback : mCallbacks) {
887                     callback.postOnPackagesAvailable(packageNames, user, replacing);
888                 }
889             }
890         }
891 
892         @Override
893         public void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing)
894                 throws RemoteException {
895             if (DEBUG) {
896                 Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames);
897             }
898             synchronized (LauncherApps.this) {
899                 for (CallbackMessageHandler callback : mCallbacks) {
900                     callback.postOnPackagesUnavailable(packageNames, user, replacing);
901                 }
902             }
903         }
904 
905         @Override
906         public void onPackagesSuspended(UserHandle user, String[] packageNames)
907                 throws RemoteException {
908             if (DEBUG) {
909                 Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + packageNames);
910             }
911             synchronized (LauncherApps.this) {
912                 for (CallbackMessageHandler callback : mCallbacks) {
913                     callback.postOnPackagesSuspended(packageNames, user);
914                 }
915             }
916         }
917 
918         @Override
919         public void onPackagesUnsuspended(UserHandle user, String[] packageNames)
920                 throws RemoteException {
921             if (DEBUG) {
922                 Log.d(TAG, "onPackagesUnsuspended " + user.getIdentifier() + "," + packageNames);
923             }
924             synchronized (LauncherApps.this) {
925                 for (CallbackMessageHandler callback : mCallbacks) {
926                     callback.postOnPackagesUnsuspended(packageNames, user);
927                 }
928             }
929         }
930 
931         @Override
932         public void onShortcutChanged(UserHandle user, String packageName,
933                 ParceledListSlice shortcuts) {
934             if (DEBUG) {
935                 Log.d(TAG, "onShortcutChanged " + user.getIdentifier() + "," + packageName);
936             }
937             final List<ShortcutInfo> list = shortcuts.getList();
938             synchronized (LauncherApps.this) {
939                 for (CallbackMessageHandler callback : mCallbacks) {
940                     callback.postOnShortcutChanged(packageName, user, list);
941                 }
942             }
943         }
944     };
945 
946     private static class CallbackMessageHandler extends Handler {
947         private static final int MSG_ADDED = 1;
948         private static final int MSG_REMOVED = 2;
949         private static final int MSG_CHANGED = 3;
950         private static final int MSG_AVAILABLE = 4;
951         private static final int MSG_UNAVAILABLE = 5;
952         private static final int MSG_SUSPENDED = 6;
953         private static final int MSG_UNSUSPENDED = 7;
954         private static final int MSG_SHORTCUT_CHANGED = 8;
955 
956         private LauncherApps.Callback mCallback;
957 
958         private static class CallbackInfo {
959             String[] packageNames;
960             String packageName;
961             boolean replacing;
962             UserHandle user;
963             List<ShortcutInfo> shortcuts;
964         }
965 
CallbackMessageHandler(Looper looper, LauncherApps.Callback callback)966         public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) {
967             super(looper, null, true);
968             mCallback = callback;
969         }
970 
971         @Override
handleMessage(Message msg)972         public void handleMessage(Message msg) {
973             if (mCallback == null || !(msg.obj instanceof CallbackInfo)) {
974                 return;
975             }
976             CallbackInfo info = (CallbackInfo) msg.obj;
977             switch (msg.what) {
978                 case MSG_ADDED:
979                     mCallback.onPackageAdded(info.packageName, info.user);
980                     break;
981                 case MSG_REMOVED:
982                     mCallback.onPackageRemoved(info.packageName, info.user);
983                     break;
984                 case MSG_CHANGED:
985                     mCallback.onPackageChanged(info.packageName, info.user);
986                     break;
987                 case MSG_AVAILABLE:
988                     mCallback.onPackagesAvailable(info.packageNames, info.user, info.replacing);
989                     break;
990                 case MSG_UNAVAILABLE:
991                     mCallback.onPackagesUnavailable(info.packageNames, info.user, info.replacing);
992                     break;
993                 case MSG_SUSPENDED:
994                     mCallback.onPackagesSuspended(info.packageNames, info.user);
995                     break;
996                 case MSG_UNSUSPENDED:
997                     mCallback.onPackagesUnsuspended(info.packageNames, info.user);
998                     break;
999                 case MSG_SHORTCUT_CHANGED:
1000                     mCallback.onShortcutsChanged(info.packageName, info.shortcuts, info.user);
1001                     break;
1002             }
1003         }
1004 
postOnPackageAdded(String packageName, UserHandle user)1005         public void postOnPackageAdded(String packageName, UserHandle user) {
1006             CallbackInfo info = new CallbackInfo();
1007             info.packageName = packageName;
1008             info.user = user;
1009             obtainMessage(MSG_ADDED, info).sendToTarget();
1010         }
1011 
postOnPackageRemoved(String packageName, UserHandle user)1012         public void postOnPackageRemoved(String packageName, UserHandle user) {
1013             CallbackInfo info = new CallbackInfo();
1014             info.packageName = packageName;
1015             info.user = user;
1016             obtainMessage(MSG_REMOVED, info).sendToTarget();
1017         }
1018 
postOnPackageChanged(String packageName, UserHandle user)1019         public void postOnPackageChanged(String packageName, UserHandle user) {
1020             CallbackInfo info = new CallbackInfo();
1021             info.packageName = packageName;
1022             info.user = user;
1023             obtainMessage(MSG_CHANGED, info).sendToTarget();
1024         }
1025 
postOnPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing)1026         public void postOnPackagesAvailable(String[] packageNames, UserHandle user,
1027                 boolean replacing) {
1028             CallbackInfo info = new CallbackInfo();
1029             info.packageNames = packageNames;
1030             info.replacing = replacing;
1031             info.user = user;
1032             obtainMessage(MSG_AVAILABLE, info).sendToTarget();
1033         }
1034 
postOnPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing)1035         public void postOnPackagesUnavailable(String[] packageNames, UserHandle user,
1036                 boolean replacing) {
1037             CallbackInfo info = new CallbackInfo();
1038             info.packageNames = packageNames;
1039             info.replacing = replacing;
1040             info.user = user;
1041             obtainMessage(MSG_UNAVAILABLE, info).sendToTarget();
1042         }
1043 
postOnPackagesSuspended(String[] packageNames, UserHandle user)1044         public void postOnPackagesSuspended(String[] packageNames, UserHandle user) {
1045             CallbackInfo info = new CallbackInfo();
1046             info.packageNames = packageNames;
1047             info.user = user;
1048             obtainMessage(MSG_SUSPENDED, info).sendToTarget();
1049         }
1050 
postOnPackagesUnsuspended(String[] packageNames, UserHandle user)1051         public void postOnPackagesUnsuspended(String[] packageNames, UserHandle user) {
1052             CallbackInfo info = new CallbackInfo();
1053             info.packageNames = packageNames;
1054             info.user = user;
1055             obtainMessage(MSG_UNSUSPENDED, info).sendToTarget();
1056         }
1057 
postOnShortcutChanged(String packageName, UserHandle user, List<ShortcutInfo> shortcuts)1058         public void postOnShortcutChanged(String packageName, UserHandle user,
1059                 List<ShortcutInfo> shortcuts) {
1060             CallbackInfo info = new CallbackInfo();
1061             info.packageName = packageName;
1062             info.user = user;
1063             info.shortcuts = shortcuts;
1064             obtainMessage(MSG_SHORTCUT_CHANGED, info).sendToTarget();
1065         }
1066     }
1067 }
1068