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
17package android.content.pm;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.TestApi;
23import android.content.ActivityNotFoundException;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.PackageManager.ApplicationInfoFlags;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.res.Resources;
30import android.graphics.Bitmap;
31import android.graphics.BitmapFactory;
32import android.graphics.Rect;
33import android.graphics.drawable.BitmapDrawable;
34import android.graphics.drawable.Drawable;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.Looper;
38import android.os.Message;
39import android.os.ParcelFileDescriptor;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.os.UserHandle;
43import android.os.UserManager;
44import android.util.DisplayMetrics;
45import android.util.Log;
46
47import java.io.IOException;
48import java.lang.annotation.Retention;
49import java.lang.annotation.RetentionPolicy;
50import java.util.ArrayList;
51import java.util.Arrays;
52import java.util.Collections;
53import 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 */
67public 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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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
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         */
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         */
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         */
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         */
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         */
321        public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
322            mQueryFlags = queryFlags;
323            return this;
324        }
325    }
326
327    /** @hide */
328    public LauncherApps(Context context, ILauncherApps service) {
329        mContext = context;
330        mService = service;
331        mPm = context.getPackageManager();
332    }
333
334    /** @hide */
335    @TestApi
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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     */
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
579    public int getShortcutIconResId(@NonNull ShortcutInfo shortcut) {
580        return shortcut.getIconResourceId();
581    }
582
583    /**
584     * @hide kept for testing.
585     */
586    @Deprecated
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     */
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     */
610    public ParcelFileDescriptor getShortcutIconFd(
611            @NonNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user) {
612        return getShortcutIconFd(packageName, shortcutId, user.getIdentifier());
613    }
614
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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. */
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
823    private void removeCallbackLocked(Callback callback) {
824        int pos = findCallbackLocked(callback);
825        if (pos >= 0) {
826            mCallbacks.remove(pos);
827        }
828    }
829
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
966        public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) {
967            super(looper, null, true);
968            mCallback = callback;
969        }
970
971        @Override
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
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
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
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
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
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
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
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
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