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