Home | History | Annotate | Download | only in pm
      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.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.PackageManager.ApplicationInfoFlags;
     27 import android.graphics.Rect;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.Looper;
     31 import android.os.Message;
     32 import android.os.ParcelFileDescriptor;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.os.UserHandle;
     36 import android.os.UserManager;
     37 import android.util.Log;
     38 
     39 import java.lang.annotation.Retention;
     40 import java.lang.annotation.RetentionPolicy;
     41 import java.util.ArrayList;
     42 import java.util.Arrays;
     43 import java.util.Collections;
     44 import 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  */
     58 public 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