content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.content.pm;
  
  import android.annotation.CallbackExecutor;
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.SdkConstant;
  import android.annotation.SdkConstant.SdkConstantType;
  import android.annotation.SystemApi;
  import android.annotation.SystemService;
  import android.annotation.TestApi;
  import android.annotation.UnsupportedAppUsage;
  import android.app.PendingIntent;
  import android.appwidget.AppWidgetManager;
  import android.appwidget.AppWidgetProviderInfo;
  import android.content.ActivityNotFoundException;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentSender;
  import android.content.pm.PackageInstaller.SessionCallback;
  import android.content.pm.PackageInstaller.SessionCallbackDelegate;
  import android.content.pm.PackageInstaller.SessionInfo;
  import android.content.pm.PackageManager.ApplicationInfoFlags;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.res.Resources;
  import android.graphics.Bitmap;
  import android.graphics.BitmapFactory;
  import android.graphics.Rect;
  import android.graphics.drawable.AdaptiveIconDrawable;
  import android.graphics.drawable.BitmapDrawable;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.Icon;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Parcel;
  import android.os.ParcelFileDescriptor;
  import android.os.Parcelable;
  import android.os.RemoteException;
  import android.os.ServiceManager;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.util.DisplayMetrics;
  import android.util.Log;
  
  import com.android.internal.util.Preconditions;
  
  import java.io.IOException;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.List;
  import java.util.concurrent.Executor;
  
  /**
   * Class for retrieving a list of launchable activities for the current user and any associated
   * managed profiles that are visible to the current user, which can be retrieved with
   * {@link #getProfiles}. This is mainly for use by launchers.
   *
   * Apps can be queried for each user profile.
   * Since the PackageManager will not deliver package broadcasts for other profiles, you can register
   * for package changes here.
   * <p>
   * To watch for managed profiles being added or removed, register for the following broadcasts:
   * {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}.
   * <p>
   * Note as of Android O, apps on a managed profile are no longer allowed to access apps on the
   * main profile.  Apps can only access profiles returned by {@link #getProfiles()}.
   */
  @SystemService(Context.LAUNCHER_APPS_SERVICE)
  public class LauncherApps {
  
      static final String TAG = "LauncherApps";
      static final boolean DEBUG = false;
  
      /**
       * Activity Action: For the default launcher to show the confirmation dialog to create
       * a pinned shortcut.
       *
       * <p>See the {@link ShortcutManager} javadoc for details.
       *
       * <p>
       * Use {@link #getPinItemRequest(Intent)} to get a {@link PinItemRequest} object,
       * and call {@link PinItemRequest#accept(Bundle)}
       * if the user accepts.  If the user doesn't accept, no further action is required.
       *
       * @see #EXTRA_PIN_ITEM_REQUEST
       */
      @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
      public static final String ACTION_CONFIRM_PIN_SHORTCUT =
              "android.content.pm.action.CONFIRM_PIN_SHORTCUT";
  
      /**
       * Activity Action: For the default launcher to show the confirmation dialog to create
       * a pinned app widget.
       *
       * <p>See the {@link android.appwidget.AppWidgetManager#requestPinAppWidget} javadoc for
       * details.
       *
       * <p>
       * Use {@link #getPinItemRequest(Intent)} to get a {@link PinItemRequest} object,
       * and call {@link PinItemRequest#accept(Bundle)}
       * if the user accepts.  If the user doesn't accept, no further action is required.
       *
       * @see #EXTRA_PIN_ITEM_REQUEST
       */
      @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
      public static final String ACTION_CONFIRM_PIN_APPWIDGET =
              "android.content.pm.action.CONFIRM_PIN_APPWIDGET";
  
      /**
       * An extra for {@link #ACTION_CONFIRM_PIN_SHORTCUT} &amp; {@link #ACTION_CONFIRM_PIN_APPWIDGET}
       * containing a {@link PinItemRequest} of appropriate type asked to pin.
       *
       * <p>A helper function {@link #getPinItemRequest(Intent)} can be used
       * instead of using this constant directly.
       *
       * @see #ACTION_CONFIRM_PIN_SHORTCUT
       * @see #ACTION_CONFIRM_PIN_APPWIDGET
       */
      public static final String EXTRA_PIN_ITEM_REQUEST =
              "android.content.pm.extra.PIN_ITEM_REQUEST";
  
      private final Context mContext;
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      private final ILauncherApps mService;
      @UnsupportedAppUsage
      private final PackageManager mPm;
      private final UserManager mUserManager;
  
      private final List<CallbackMessageHandler> mCallbacks = new ArrayList<>();
      private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>();
  
      /**
       * Callbacks for package changes to this and related managed profiles.
       */
      public static abstract class Callback {
          /**
           * Indicates that a package was removed from the specified profile.
           *
           * If a package is removed while being updated onPackageChanged will be
           * called instead.
           *
           * @param packageName The name of the package that was removed.
           * @param user The UserHandle of the profile that generated the change.
           */
          abstract public void onPackageRemoved(String packageName, UserHandle user);
  
          /**
           * Indicates that a package was added to the specified profile.
           *
           * If a package is added while being updated then onPackageChanged will be
           * called instead.
           *
           * @param packageName The name of the package that was added.
           * @param user The UserHandle of the profile that generated the change.
           */
          abstract public void onPackageAdded(String packageName, UserHandle user);
  
          /**
           * Indicates that a package was modified in the specified profile.
           * This can happen, for example, when the package is updated or when
           * one or more components are enabled or disabled.
           *
           * @param packageName The name of the package that has changed.
           * @param user The UserHandle of the profile that generated the change.
           */
          abstract public void onPackageChanged(String packageName, UserHandle user);
  
          /**
           * Indicates that one or more packages have become available. For
           * example, this can happen when a removable storage card has
           * reappeared.
           *
           * @param packageNames The names of the packages that have become
           *            available.
           * @param user The UserHandle of the profile that generated the change.
           * @param replacing Indicates whether these packages are replacing
           *            existing ones.
           */
          abstract public void onPackagesAvailable(String[] packageNames, UserHandle user,
                  boolean replacing);
  
          /**
           * Indicates that one or more packages have become unavailable. For
           * example, this can happen when a removable storage card has been
           * removed.
           *
           * @param packageNames The names of the packages that have become
           *            unavailable.
           * @param user The UserHandle of the profile that generated the change.
           * @param replacing Indicates whether the packages are about to be
           *            replaced with new versions.
           */
          abstract public void onPackagesUnavailable(String[] packageNames, UserHandle user,
                  boolean replacing);
  
          /**
           * Indicates that one or more packages have been suspended. For
           * example, this can happen when a Device Administrator suspends
           * an applicaton.
           *
           * <p>Note: On devices running {@link android.os.Build.VERSION_CODES#P Android P} or higher,
           * any apps that override {@link #onPackagesSuspended(String[], UserHandle, Bundle)} will
           * not receive this callback.
           *
           * @param packageNames The names of the packages that have just been
           *            suspended.
           * @param user The UserHandle of the profile that generated the change.
           */
          public void onPackagesSuspended(String[] packageNames, UserHandle user) {
          }
  
          /**
           * Indicates that one or more packages have been suspended. A device administrator or an app
           * with {@code android.permission.SUSPEND_APPS} can do this.
           *
           * <p>A suspending app with the permission {@code android.permission.SUSPEND_APPS} can
           * optionally provide a {@link Bundle} of extra information that it deems helpful for the
           * launcher to handle the suspended state of these packages. The contents of this
           * {@link Bundle} are supposed to be a contract between the suspending app and the launcher.
           *
           * @param packageNames The names of the packages that have just been suspended.
           * @param user the user for which the given packages were suspended.
           * @param launcherExtras A {@link Bundle} of extras for the launcher, if provided to the
           *                      system, {@code null} otherwise.
           * @see PackageManager#isPackageSuspended()
           * @see #getSuspendedPackageLauncherExtras(String, UserHandle)
           */
          public void onPackagesSuspended(String[] packageNames, UserHandle user,
                  @Nullable Bundle launcherExtras) {
              onPackagesSuspended(packageNames, user);
          }
  
          /**
           * Indicates that one or more packages have been unsuspended. For
           * example, this can happen when a Device Administrator unsuspends
           * an applicaton.
           *
           * @param packageNames The names of the packages that have just been
           *            unsuspended.
           * @param user The UserHandle of the profile that generated the change.
           */
          public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
          }
  
          /**
           * Indicates that one or more shortcuts of any kind (dynamic, pinned, or manifest)
           * have been added, updated or removed.
           *
           * <p>Only the applications that are allowed to access the shortcut information,
           * as defined in {@link #hasShortcutHostPermission()}, will receive it.
           *
           * @param packageName The name of the package that has the shortcuts.
           * @param shortcuts All shortcuts from the package (dynamic, manifest and/or pinned).
           *    Only "key" information will be provided, as defined in
           *    {@link ShortcutInfo#hasKeyFieldsOnly()}.
           * @param user The UserHandle of the profile that generated the change.
           *
           * @see ShortcutManager
           */
          public void onShortcutsChanged(@NonNull String packageName,
                  @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
          }
      }
  
      /**
       * Represents a query passed to {@link #getShortcuts(ShortcutQuery, UserHandle)}.
       */
      public static class ShortcutQuery {
          /**
           * Include dynamic shortcuts in the result.
           */
          public static final int FLAG_MATCH_DYNAMIC = 1 << 0;
  
          /** @hide kept for unit tests */
          @Deprecated
          public static final int FLAG_GET_DYNAMIC = FLAG_MATCH_DYNAMIC;
  
          /**
           * Include pinned shortcuts in the result.
           *
           * <p>If you are the selected assistant app, and wishes to fetch all shortcuts that the
           * user owns on the launcher (or by other launchers, in case the user has multiple), use
           * {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} instead.
           *
           * <p>If you're a regular launcher app, there's no way to get shortcuts pinned by other
           * launchers, and {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} will be ignored. So use this
           * flag to get own pinned shortcuts.
           */
          public static final int FLAG_MATCH_PINNED = 1 << 1;
  
          /** @hide kept for unit tests */
          @Deprecated
          public static final int FLAG_GET_PINNED = FLAG_MATCH_PINNED;
  
          /**
           * Include manifest shortcuts in the result.
           */
          public static final int FLAG_MATCH_MANIFEST = 1 << 3;
  
          /** @hide kept for unit tests */
          @Deprecated
          public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
  
          /**
           * Include all pinned shortcuts by any launchers, not just by the caller,
           * in the result.
           *
           * <p>The caller must be the selected assistant app to use this flag, or have the system
           * {@code ACCESS_SHORTCUTS} permission.
           *
           * <p>If you are the selected assistant app, and wishes to fetch all shortcuts that the
           * user owns on the launcher (or by other launchers, in case the user has multiple), use
           * {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} instead.
           *
           * <p>If you're a regular launcher app (or any app that's not the selected assistant app)
           * then this flag will be ignored.
           */
          public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1 << 10;
  
          /**
           * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
           * @hide
           */
          public static final int FLAG_MATCH_ALL_KINDS =
                  FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
  
          /**
           * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_ALL_PINNED
           * @hide
           */
          public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
                  FLAG_MATCH_ALL_KINDS | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
  
          /** @hide kept for unit tests */
          @Deprecated
          public static final int FLAG_GET_ALL_KINDS = FLAG_MATCH_ALL_KINDS;
  
          /**
           * Requests "key" fields only.  See {@link ShortcutInfo#hasKeyFieldsOnly()}'s javadoc to
           * see which fields fields "key".
           * This allows quicker access to shortcut information in order to
           * determine whether the caller's in-memory cache needs to be updated.
           *
           * <p>Typically, launcher applications cache all or most shortcut information
           * in memory in order to show shortcuts without a delay.
           *
           * When a given launcher application wants to update its cache, such as when its process
           * restarts, it can fetch shortcut information with this flag.
           * The application can then check {@link ShortcutInfo#getLastChangedTimestamp()} for each
           * shortcut, fetching a shortcut's non-key information only if that shortcut has been
           * updated.
           *
           * @see ShortcutManager
           */
          public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2;
  
          /** @hide */
          @IntDef(flag = true, prefix = { "FLAG_" }, value = {
                  FLAG_MATCH_DYNAMIC,
                  FLAG_MATCH_PINNED,
                  FLAG_MATCH_MANIFEST,
                  FLAG_GET_KEY_FIELDS_ONLY,
                  FLAG_MATCH_MANIFEST,
          })
          @Retention(RetentionPolicy.SOURCE)
          public @interface QueryFlags {}
  
          long mChangedSince;
  
          @Nullable
          String mPackage;
  
          @Nullable
          List<String> mShortcutIds;
  
          @Nullable
          ComponentName mActivity;
  
          @QueryFlags
          int mQueryFlags;
  
          public ShortcutQuery() {
          }
  
          /**
           * If non-zero, returns only shortcuts that have been added or updated
           * since the given timestamp, expressed in milliseconds since the Epoch&mdash;see
           * {@link System#currentTimeMillis()}.
           */
          public ShortcutQuery setChangedSince(long changedSince) {
              mChangedSince = changedSince;
              return this;
          }
  
          /**
           * If non-null, returns only shortcuts from the package.
           */
          public ShortcutQuery setPackage(@Nullable String packageName) {
              mPackage = packageName;
              return this;
          }
  
          /**
           * If non-null, return only the specified shortcuts by ID.  When setting this field,
           * a package name must also be set with {@link #setPackage}.
           */
          public ShortcutQuery setShortcutIds(@Nullable List<String> shortcutIds) {
              mShortcutIds = shortcutIds;
              return this;
          }
  
          /**
           * If non-null, returns only shortcuts associated with the activity; i.e.
           * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
           * to {@code activity}.
           */
          public ShortcutQuery setActivity(@Nullable ComponentName activity) {
              mActivity = activity;
              return this;
          }
  
          /**
           * Set query options.  At least one of the {@code MATCH} flags should be set.  Otherwise,
           * no shortcuts will be returned.
           *
           * <ul>
           *     <li>{@link #FLAG_MATCH_DYNAMIC}
           *     <li>{@link #FLAG_MATCH_PINNED}
           *     <li>{@link #FLAG_MATCH_MANIFEST}
           *     <li>{@link #FLAG_GET_KEY_FIELDS_ONLY}
           * </ul>
           */
          public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
              mQueryFlags = queryFlags;
              return this;
          }
      }
  
      /** @hide */
      public LauncherApps(Context context, ILauncherApps service) {
          mContext = context;
          mService = service;
          mPm = context.getPackageManager();
          mUserManager = context.getSystemService(UserManager.class);
      }
  
      /** @hide */
      @TestApi
      public LauncherApps(Context context) {
          this(context, ILauncherApps.Stub.asInterface(
                  ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE)));
      }
  
      /**
       * Show an error log on logcat, when the calling user is a managed profile, and the target
       * user is different from the calling user, in order to help developers to detect it.
       */
      private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) {
          if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()) {
              Log.w(TAG, "Accessing other profiles/users from managed profile is no longer allowed.");
          }
      }
  
      /**
       * Return a list of profiles that the caller can access via the {@link LauncherApps} APIs.
       *
       * <p>If the caller is running on a managed profile, it'll return only the current profile.
       * Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
       */
      public List<UserHandle> getProfiles() {
          if (mUserManager.isManagedProfile()) {
              // If it's a managed profile, only return the current profile.
              final List result =  new ArrayList(1);
              result.add(android.os.Process.myUserHandle());
              return result;
          } else {
              return mUserManager.getUserProfiles();
          }
      }
  
      /**
       * Retrieves a list of activities that specify {@link Intent#ACTION_MAIN} and
       * {@link Intent#CATEGORY_LAUNCHER}, across all apps, for a specified user. If an app doesn't
       * have any activities that specify <code>ACTION_MAIN</code> or <code>CATEGORY_LAUNCHER</code>,
       * the system adds a synthesized activity to the list. This synthesized activity represents the
       * app's details page within system settings.
       *
       * <p class="note"><b>Note: </b>It's possible for system apps, such as app stores, to prevent
       * the system from adding synthesized activities to the returned list.</p>
       *
       * <p>As of <a href="/reference/android/os/Build.VERSION_CODES.html#Q">Android Q</a>, at least
       * one of the app's activities or synthesized activities appears in the returned list unless the
       * app satisfies at least one of the following conditions:</p>
       * <ul>
       * <li>The app is a system app.</li>
       * <li>The app doesn't request any <a href="/guide/topics/permissions/overview">permissions</a>.
       * </li>
       * <li>The app doesn't have a <em>launcher activity</em> that is enabled by default. A launcher
       * activity has an intent containing the <code>ACTION_MAIN</code> action and the
       * <code>CATEGORY_LAUNCHER</code> category.</li>
       * </ul>
       *
       * <p>Additionally, the system hides synthesized activities for some or all apps in the
       * following enterprise-related cases:</p>
       * <ul>
       * <li>If the device is a
       * <a href="https://developers.google.com/android/work/overview#company-owned-devices-for-knowledge-workers">fully
       * managed device</a>, no synthesized activities for any app appear in the returned list.</li>
       * <li>If the current user has a
       * <a href="https://developers.google.com/android/work/overview#employee-owned-devices-byod">work
       * profile</a>, no synthesized activities for the user's work apps appear in the returned
       * list.</li>
       * </ul>
       *
       * @param packageName The specific package to query. If null, it checks all installed packages
       *            in the profile.
       * @param user The UserHandle of the profile.
       * @return List of launchable activities. Can be an empty list but will not be null.
       */
      public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
          logErrorForInvalidProfileAccess(user);
          try {
              return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(),
                      packageName, user), user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
       * returns null.
       *
       * @param intent The intent to find a match for.
       * @param user The profile to look in for a match.
       * @return An activity info object if there is a match.
       */
      public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
          logErrorForInvalidProfileAccess(user);
          try {
              ActivityInfo ai = mService.resolveActivity(mContext.getPackageName(),
                      intent.getComponent(), user);
              if (ai != null) {
                  LauncherActivityInfo info = new LauncherActivityInfo(mContext, ai, user);
                  return info;
              }
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
          return null;
      }
  
      /**
       * Starts a Main activity in the specified profile.
       *
       * @param component The ComponentName of the activity to launch
       * @param user The UserHandle of the profile
       * @param sourceBounds The Rect containing the source bounds of the clicked icon
       * @param opts Options to pass to startActivity
       */
      public void startMainActivity(ComponentName component, UserHandle user, Rect sourceBounds,
              Bundle opts) {
          logErrorForInvalidProfileAccess(user);
          if (DEBUG) {
              Log.i(TAG, "StartMainActivity " + component + " " + user.getIdentifier());
          }
          try {
              mService.startActivityAsUser(mContext.getIApplicationThread(),
                      mContext.getPackageName(),
                      component, sourceBounds, opts, user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Starts an activity to show the details of the specified session.
       *
       * @param sessionInfo The SessionInfo of the session
       * @param sourceBounds The Rect containing the source bounds of the clicked icon
       * @param opts Options to pass to startActivity
       */
      public void startPackageInstallerSessionDetailsActivity(@NonNull SessionInfo sessionInfo,
              @Nullable Rect sourceBounds, @Nullable Bundle opts) {
          try {
              mService.startSessionDetailsActivityAsUser(mContext.getIApplicationThread(),
                      mContext.getPackageName(), sessionInfo, sourceBounds, opts,
                      sessionInfo.getUser());
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Starts the settings activity to show the application details for a
       * package in the specified profile.
       *
       * @param component The ComponentName of the package to launch settings for.
       * @param user The UserHandle of the profile
       * @param sourceBounds The Rect containing the source bounds of the clicked icon
       * @param opts Options to pass to startActivity
       */
      public void startAppDetailsActivity(ComponentName component, UserHandle user,
              Rect sourceBounds, Bundle opts) {
          logErrorForInvalidProfileAccess(user);
          try {
              mService.showAppDetailsAsUser(mContext.getIApplicationThread(),
                      mContext.getPackageName(),
                      component, sourceBounds, opts, user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Retrieves a list of config activities for creating {@link ShortcutInfo}.
       *
       * @param packageName The specific package to query. If null, it checks all installed packages
       *            in the profile.
       * @param user The UserHandle of the profile.
       * @return List of config activities. Can be an empty list but will not be null.
       *
       * @see Intent#ACTION_CREATE_SHORTCUT
       * @see #getShortcutConfigActivityIntent(LauncherActivityInfo)
       */
      public List<LauncherActivityInfo> getShortcutConfigActivityList(@Nullable String packageName,
              @NonNull UserHandle user) {
          logErrorForInvalidProfileAccess(user);
          try {
              return convertToActivityList(mService.getShortcutConfigActivities(
                      mContext.getPackageName(), packageName, user),
                      user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      private List<LauncherActivityInfo> convertToActivityList(
              @Nullable ParceledListSlice<ResolveInfo> activities, UserHandle user) {
          if (activities == null) {
              return Collections.EMPTY_LIST;
          }
          ArrayList<LauncherActivityInfo> lais = new ArrayList<>();
          for (ResolveInfo ri : activities.getList()) {
              LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri.activityInfo, user);
              if (DEBUG) {
                  Log.v(TAG, "Returning activity for profile " + user + " : "
                          + lai.getComponentName());
              }
              lais.add(lai);
          }
          return lais;
      }
  
      /**
       * Returns an intent sender which can be used to start the configure activity for creating
       * custom shortcuts. Use this method if the provider is in another profile as you are not
       * allowed to start an activity in another profile.
       *
       * <p>The caller should receive {@link PinItemRequest} in onActivityResult on
       * {@link android.app.Activity#RESULT_OK}.
       *
       * <p>Callers must be allowed to access the shortcut information, as defined in {@link
       * #hasShortcutHostPermission()}.
       *
       * @param info a configuration activity returned by {@link #getShortcutConfigActivityList}
       *
       * @throws IllegalStateException when the user is locked or not running.
       * @throws SecurityException if {@link #hasShortcutHostPermission()} is false.
       *
       * @see #getPinItemRequest(Intent)
       * @see Intent#ACTION_CREATE_SHORTCUT
       * @see android.app.Activity#startIntentSenderForResult
       */
      @Nullable
      public IntentSender getShortcutConfigActivityIntent(@NonNull LauncherActivityInfo info) {
          try {
              return mService.getShortcutConfigActivityIntent(
                      mContext.getPackageName(), info.getComponentName(), info.getUser());
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Checks if the package is installed and enabled for a profile.
       *
       * @param packageName The package to check.
       * @param user The UserHandle of the profile.
       *
       * @return true if the package exists and is enabled.
       */
      public boolean isPackageEnabled(String packageName, UserHandle user) {
          logErrorForInvalidProfileAccess(user);
          try {
              return mService.isPackageEnabled(mContext.getPackageName(), packageName, user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Gets the launcher extras supplied to the system when the given package was suspended via
       * {@code PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
       * PersistableBundle, String)}.
       *
       * <p>The contents of this {@link Bundle} are supposed to be a contract between the suspending
       * app and the launcher.
       *
       * <p>Note: This just returns whatever extras were provided to the system, <em>which might
       * even be {@code null}.</em>
       *
       * @param packageName The package for which to fetch the launcher extras.
       * @param user The {@link UserHandle} of the profile.
       * @return A {@link Bundle} of launcher extras. Or {@code null} if the package is not currently
       *         suspended.
       *
       * @see Callback#onPackagesSuspended(String[], UserHandle, Bundle)
       * @see PackageManager#isPackageSuspended()
       */
      public @Nullable Bundle getSuspendedPackageLauncherExtras(String packageName, UserHandle user) {
          logErrorForInvalidProfileAccess(user);
          try {
              return mService.getSuspendedPackageLauncherExtras(packageName, user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Returns whether a package should be hidden from suggestions to the user. Currently, this
       * could be done because the package was marked as distracting to the user via
       * {@code PackageManager.setDistractingPackageRestrictions(String[], int)}.
       *
       * @param packageName The package for which to check.
       * @param user the {@link UserHandle} of the profile.
       * @return
       */
      public boolean shouldHideFromSuggestions(@NonNull String packageName,
              @NonNull UserHandle user) {
          Preconditions.checkNotNull(packageName, "packageName");
          Preconditions.checkNotNull(user, "user");
          try {
              return mService.shouldHideFromSuggestions(packageName, user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Returns {@link ApplicationInfo} about an application installed for a specific user profile.
       *
       * @param packageName The package name of the application
       * @param flags Additional option flags {@link PackageManager#getApplicationInfo}
       * @param user The UserHandle of the profile.
       *
       * @return {@link ApplicationInfo} containing information about the package. Returns
       *         {@code null} if the package isn't installed for the given profile, or the profile
       *         isn't enabled.
       */
      public ApplicationInfo getApplicationInfo(@NonNull String packageName,
              @ApplicationInfoFlags int flags, @NonNull UserHandle user)
              throws PackageManager.NameNotFoundException {
          Preconditions.checkNotNull(packageName, "packageName");
          Preconditions.checkNotNull(user, "user");
          logErrorForInvalidProfileAccess(user);
          try {
              final ApplicationInfo ai = mService
                      .getApplicationInfo(mContext.getPackageName(), packageName, flags, user);
              if (ai == null) {
                  throw new NameNotFoundException("Package " + packageName + " not found for user "
                          + user.getIdentifier());
              }
              return ai;
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Returns an object describing the app usage limit for the given package.
       * If there are multiple limits that apply to the package, the one with the smallest
       * time remaining will be returned.
       *
       * @param packageName name of the package whose app usage limit will be returned
       * @param user the user of the package
       *
       * @return an {@link AppUsageLimit} object describing the app time limit containing
       * the given package with the smallest time remaining, or {@code null} if none exist.
       * @throws SecurityException when the caller is not the recents app.
       * @hide
       */
      @Nullable
      @SystemApi
      public LauncherApps.AppUsageLimit getAppUsageLimit(@NonNull String packageName,
              @NonNull UserHandle user) {
          try {
              return mService.getAppUsageLimit(mContext.getPackageName(), packageName, user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Checks if the activity exists and it enabled for a profile.
       *
       * <p>The activity may still not be exported, in which case {@link #startMainActivity} will
       * throw a {@link SecurityException} unless the caller has the same UID as the target app's.
       *
       * @param component The activity to check.
       * @param user The UserHandle of the profile.
       *
       * @return true if the activity exists and is enabled.
       */
      public boolean isActivityEnabled(ComponentName component, UserHandle user) {
          logErrorForInvalidProfileAccess(user);
          try {
              return mService.isActivityEnabled(mContext.getPackageName(), component, user);
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      /**
       * Returns whether the caller can access the shortcut information.  Access is currently
       * available to:
       *
       * <ul>
       *     <li>The current launcher (or default launcher if there is no set current launcher).</li>
       *     <li>The currently active voice interaction service.</li>
       * </ul>
       *
       * <p>Note when this method returns {@code false}, it may be a temporary situation because
       * the user is trying a new launcher application.  The user may decide to change the default
       * launcher back to the calling application again, so even if a launcher application loses
       * this permission, it does <b>not</b> have to purge pinned shortcut information.
       * If the calling launcher application contains pinned shortcuts, they will still work,
       * even though the caller no longer has the shortcut host permission.
       *
       * @throws IllegalStateException when the user is locked.
       *
       * @see ShortcutManager
       */
      public boolean hasShortcutHostPermission() {
          try {
              return mService.hasShortcutHostPermission(mContext.getPackageName());
          } catch (RemoteException re) {
              throw re.rethrowFromSystemServer();
          }
      }
  
      private List<ShortcutInfo> maybeUpdateDisabledMessage(List<ShortcutInfo> shortcuts) {
          if (shortcuts == null) {
              return null;
          }
          for (int i = shortcuts.size() - 1; i >= 0; i--) {
              final ShortcutInfo si = shortcuts.get(i);
              final String message = ShortcutInfo.getDisabledReasonForRestoreIssue(mContext,
                      si.getDisabledReason());
              if (message != null) {
                  si.setDisabledMessage(message);
              }
          }
          return shortcuts;
      }
  
      /**
       * Returns {@link ShortcutInfo}s that match {@code query}.
       *
       * <p>Callers must be allowed to access the shortcut information, as defined in {@link
       * #hasShortcutHostPermission()}.
       *
       * @param query result includes shortcuts matching this query.
       * @param user The UserHandle of the profile.
       *
       * @return the IDs of {@link ShortcutInfo}s that match the query.
       * @throws IllegalStateException when the user is locked, or when the {@code user} user
       * is locked or not running.
       *
       * @see ShortcutManager
       */
      @Nullable
      public List<ShortcutInfo> getShortcuts(@NonNull ShortcutQuery query,
              @NonNull UserHandle user) {
          logErrorForInvalidProfileAccess(user);
          try {
              // Note this is the only case we need to update the disabled message for shortcuts
              // that weren't restored.
              // The restore problem messages are only shown by the user, and publishers will never
              // see them. The only other API that the launcher gets shortcuts is the shortcut
              // changed callback, but that only returns shortcuts with the "key" information, so
              // that won't return disabled message.
              return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
                      query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
                      query.mQueryFlags, user)
                      .getList());
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * @hide // No longer used.  Use getShortcuts() instead.  Kept for unit tests.
       */
      @Nullable
      @Deprecated
      public List<ShortcutInfo> getShortcutInfo(@NonNull String packageName,
              @NonNull List<String> ids, @NonNull UserHandle user) {
          final ShortcutQuery q = new ShortcutQuery();
          q.setPackage(packageName);
          q.setShortcutIds(ids);
          q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
          return getShortcuts(q, user);
      }
  
      /**
       * Pin shortcuts on a package.
       *
       * <p>This API is <b>NOT</b> cumulative; this will replace all pinned shortcuts for the package.
       * However, different launchers may have different set of pinned shortcuts.
       *
       * <p>The calling launcher application must be allowed to access the shortcut information,
       * as defined in {@link #hasShortcutHostPermission()}.
       *
       * @param packageName The target package name.
       * @param shortcutIds The IDs of the shortcut to be pinned.
       * @param user The UserHandle of the profile.
       * @throws IllegalStateException when the user is locked, or when the {@code user} user
       * is locked or not running.
       *
       * @see ShortcutManager
       */
      public void pinShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
              @NonNull UserHandle user) {
          logErrorForInvalidProfileAccess(user);
          try {
              mService.pinShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * @hide kept for testing.
       */
      @Deprecated
      public int getShortcutIconResId(@NonNull ShortcutInfo shortcut) {
          return shortcut.getIconResourceId();
      }
  
      /**
       * @hide kept for testing.
       */
      @Deprecated
      public int getShortcutIconResId(@NonNull String packageName, @NonNull String shortcutId,
              @NonNull UserHandle user) {
          final ShortcutQuery q = new ShortcutQuery();
          q.setPackage(packageName);
          q.setShortcutIds(Arrays.asList(shortcutId));
          q.setQueryFlags(ShortcutQuery.FLAG_GET_ALL_KINDS);
          final List<ShortcutInfo> shortcuts = getShortcuts(q, user);
  
          return shortcuts.size() > 0 ? shortcuts.get(0).getIconResourceId() : 0;
      }
  
      /**
       * @hide internal/unit tests only
       */
      public ParcelFileDescriptor getShortcutIconFd(
              @NonNull ShortcutInfo shortcut) {
          return getShortcutIconFd(shortcut.getPackage(), shortcut.getId(),
                  shortcut.getUserId());
      }
  
      /**
       * @hide internal/unit tests only
       */
      public ParcelFileDescriptor getShortcutIconFd(
              @NonNull String packageName, @NonNull String shortcutId, @NonNull UserHandle user) {
          return getShortcutIconFd(packageName, shortcutId, user.getIdentifier());
      }
  
      private ParcelFileDescriptor getShortcutIconFd(
              @NonNull String packageName, @NonNull String shortcutId, int userId) {
          try {
              return mService.getShortcutIconFd(mContext.getPackageName(),
                      packageName, shortcutId, userId);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Returns the icon for this shortcut, without any badging for the profile.
       *
       * <p>The calling launcher application must be allowed to access the shortcut information,
       * as defined in {@link #hasShortcutHostPermission()}.
       *
       * @param density The preferred density of the icon, zero for default density. Use
       * density DPI values from {@link DisplayMetrics}.
       *
       * @return The drawable associated with the shortcut.
       * @throws IllegalStateException when the user is locked, or when the {@code user} user
       * is locked or not running.
       *
       * @see ShortcutManager
       * @see #getShortcutBadgedIconDrawable(ShortcutInfo, int)
       * @see DisplayMetrics
       */
      public Drawable getShortcutIconDrawable(@NonNull ShortcutInfo shortcut, int density) {
          if (shortcut.hasIconFile()) {
              final ParcelFileDescriptor pfd = getShortcutIconFd(shortcut);
              if (pfd == null) {
                  return null;
              }
              try {
                  final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
                  if (bmp != null) {
                      BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp);
                      if (shortcut.hasAdaptiveBitmap()) {
                          return new AdaptiveIconDrawable(null, dr);
                      } else {
                          return dr;
                      }
                  }
                  return null;
              } finally {
                  try {
                      pfd.close();
                  } catch (IOException ignore) {
                  }
              }
          } else if (shortcut.hasIconResource()) {
              return loadDrawableResourceFromPackage(shortcut.getPackage(),
                      shortcut.getIconResourceId(), shortcut.getUserHandle(), density);
          } else if (shortcut.getIcon() != null) {
              // This happens if a shortcut is pending-approval.
              final Icon icon = shortcut.getIcon();
              switch (icon.getType()) {
                  case Icon.TYPE_RESOURCE: {
                      return loadDrawableResourceFromPackage(shortcut.getPackage(),
                              icon.getResId(), shortcut.getUserHandle(), density);
                  }
                  case Icon.TYPE_BITMAP:
                  case Icon.TYPE_ADAPTIVE_BITMAP: {
                      return icon.loadDrawable(mContext);
                  }
                  default:
                      return null; // Shouldn't happen though.
              }
          } else {
              return null; // Has no icon.
          }
      }
  
      private Drawable loadDrawableResourceFromPackage(String packageName, int resId,
              UserHandle user, int density) {
          try {
              if (resId == 0) {
                  return null; // Shouldn't happen but just in case.
              }
              final ApplicationInfo ai = getApplicationInfo(packageName, /* flags =*/ 0, user);
              final Resources res = mContext.getPackageManager().getResourcesForApplication(ai);
              return res.getDrawableForDensity(resId, density);
          } catch (NameNotFoundException | Resources.NotFoundException e) {
              return null;
          }
      }
  
      /**
       * Returns the shortcut icon with badging appropriate for the profile.
       *
       * <p>The calling launcher application must be allowed to access the shortcut information,
       * as defined in {@link #hasShortcutHostPermission()}.
       *
       * @param density Optional density for the icon, or 0 to use the default density. Use
       * @return A badged icon for the shortcut.
       * @throws IllegalStateException when the user is locked, or when the {@code user} user
       * is locked or not running.
       *
       * @see ShortcutManager
       * @see #getShortcutIconDrawable(ShortcutInfo, int)
       * @see DisplayMetrics
       */
      public Drawable getShortcutBadgedIconDrawable(ShortcutInfo shortcut, int density) {
          final Drawable originalIcon = getShortcutIconDrawable(shortcut, density);
  
          return (originalIcon == null) ? null : mContext.getPackageManager().getUserBadgedIcon(
                  originalIcon, shortcut.getUserHandle());
      }
  
      /**
       * Starts a shortcut.
       *
       * <p>The calling launcher application must be allowed to access the shortcut information,
       * as defined in {@link #hasShortcutHostPermission()}.
       *
       * @param packageName The target shortcut package name.
       * @param shortcutId The target shortcut ID.
       * @param sourceBounds The Rect containing the source bounds of the clicked icon.
       * @param startActivityOptions Options to pass to startActivity.
       * @param user The UserHandle of the profile.
       * @throws IllegalStateException when the user is locked, or when the {@code user} user
       * is locked or not running.
       *
       * @throws android.content.ActivityNotFoundException failed to start shortcut. (e.g.
       * the shortcut no longer exists, is disabled, the intent receiver activity doesn't exist, etc)
       */
      public void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
              @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
              @NonNull UserHandle user) {
          logErrorForInvalidProfileAccess(user);
  
          startShortcut(packageName, shortcutId, sourceBounds, startActivityOptions,
                  user.getIdentifier());
      }
  
      /**
       * Launches a shortcut.
       *
       * <p>The calling launcher application must be allowed to access the shortcut information,
       * as defined in {@link #hasShortcutHostPermission()}.
       *
       * @param shortcut The target shortcut.
       * @param sourceBounds The Rect containing the source bounds of the clicked icon.
       * @param startActivityOptions Options to pass to startActivity.
       * @throws IllegalStateException when the user is locked, or when the {@code user} user
       * is locked or not running.
       *
       * @throws android.content.ActivityNotFoundException failed to start shortcut. (e.g.
       * the shortcut no longer exists, is disabled, the intent receiver activity doesn't exist, etc)
       */
      public void startShortcut(@NonNull ShortcutInfo shortcut,
              @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
          startShortcut(shortcut.getPackage(), shortcut.getId(),
                  sourceBounds, startActivityOptions,
                  shortcut.getUserId());
      }
  
      @UnsupportedAppUsage
      private void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
              @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
              int userId) {
          try {
              final boolean success =
                      mService.startShortcut(mContext.getPackageName(), packageName, shortcutId,
                      sourceBounds, startActivityOptions, userId);
              if (!success) {
                  throw new ActivityNotFoundException("Shortcut could not be started");
              }
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Registers a callback for changes to packages in this user and managed profiles.
       *
       * @param callback The callback to register.
       */
      public void registerCallback(Callback callback) {
          registerCallback(callback, null);
      }
  
      /**
       * Registers a callback for changes to packages in this user and managed profiles.
       *
       * @param callback The callback to register.
       * @param handler that should be used to post callbacks on, may be null.
       */
      public void registerCallback(Callback callback, Handler handler) {
          synchronized (this) {
              if (callback != null && findCallbackLocked(callback) < 0) {
                  boolean addedFirstCallback = mCallbacks.size() == 0;
                  addCallbackLocked(callback, handler);
                  if (addedFirstCallback) {
                      try {
                          mService.addOnAppsChangedListener(mContext.getPackageName(),
                                  mAppsChangedListener);
                      } catch (RemoteException re) {
                          throw re.rethrowFromSystemServer();
                      }
                  }
              }
          }
      }
  
      /**
       * Unregisters a callback that was previously registered.
       *
       * @param callback The callback to unregister.
       * @see #registerCallback(Callback)
       */
      public void unregisterCallback(Callback callback) {
          synchronized (this) {
              removeCallbackLocked(callback);
              if (mCallbacks.size() == 0) {
                  try {
                      mService.removeOnAppsChangedListener(mAppsChangedListener);
                  } catch (RemoteException re) {
                      throw re.rethrowFromSystemServer();
                  }
              }
          }
      }
  
      /** @return position in mCallbacks for callback or -1 if not present. */
      private int findCallbackLocked(Callback callback) {
          if (callback == null) {
              throw new IllegalArgumentException("Callback cannot be null");
          }
          final int size = mCallbacks.size();
          for (int i = 0; i < size; ++i) {
              if (mCallbacks.get(i).mCallback == callback) {
                  return i;
              }
          }
          return -1;
      }
  
      private void removeCallbackLocked(Callback callback) {
          int pos = findCallbackLocked(callback);
          if (pos >= 0) {
              mCallbacks.remove(pos);
          }
      }
  
      private void addCallbackLocked(Callback callback, Handler handler) {
          // Remove if already present.
          removeCallbackLocked(callback);
          if (handler == null) {
              handler = new Handler();
          }
          CallbackMessageHandler toAdd = new CallbackMessageHandler(handler.getLooper(), callback);
          mCallbacks.add(toAdd);
      }
  
      private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {
  
          @Override
          public void onPackageRemoved(UserHandle user, String packageName)
                  throws RemoteException {
              if (DEBUG) {
                  Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName);
              }
              synchronized (LauncherApps.this) {
                  for (CallbackMessageHandler callback : mCallbacks) {
                      callback.postOnPackageRemoved(packageName, user);
                  }
              }
          }
  
          @Override
          public void onPackageChanged(UserHandle user, String packageName) throws RemoteException {
              if (DEBUG) {
                  Log.d(TAG, "onPackageChanged " + user.getIdentifier() + "," + packageName);
              }
              synchronized (LauncherApps.this) {
                  for (CallbackMessageHandler callback : mCallbacks) {
                      callback.postOnPackageChanged(packageName, user);
                  }
              }
          }
  
          @Override
          public void onPackageAdded(UserHandle user, String packageName) throws RemoteException {
              if (DEBUG) {
                  Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName);
              }
              synchronized (LauncherApps.this) {
                  for (CallbackMessageHandler callback : mCallbacks) {
                      callback.postOnPackageAdded(packageName, user);
                  }
              }
          }
  
          @Override
          public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing)
                  throws RemoteException {
              if (DEBUG) {
                  Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames);
              }
              synchronized (LauncherApps.this) {
                  for (CallbackMessageHandler callback : mCallbacks) {
                      callback.postOnPackagesAvailable(packageNames, user, replacing);
                  }
              }
          }
  
          @Override
          public void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing)
                  throws RemoteException {
              if (DEBUG) {
                  Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames);
              }
              synchronized (LauncherApps.this) {
                  for (CallbackMessageHandler callback : mCallbacks) {
                      callback.postOnPackagesUnavailable(packageNames, user, replacing);
                  }
              }
          }
  
          @Override
          public void onPackagesSuspended(UserHandle user, String[] packageNames,
                  Bundle launcherExtras)
                  throws RemoteException {
              if (DEBUG) {
                  Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + packageNames);
              }
              synchronized (LauncherApps.this) {
                  for (CallbackMessageHandler callback : mCallbacks) {
                      callback.postOnPackagesSuspended(packageNames, launcherExtras, user);
                  }
              }
          }
  
          @Override
          public void onPackagesUnsuspended(UserHandle user, String[] packageNames)
                  throws RemoteException {
              if (DEBUG) {
                  Log.d(TAG, "onPackagesUnsuspended " + user.getIdentifier() + "," + packageNames);
              }
              synchronized (LauncherApps.this) {
                  for (CallbackMessageHandler callback : mCallbacks) {
                      callback.postOnPackagesUnsuspended(packageNames, user);
                  }
              }
          }
  
          @Override
          public void onShortcutChanged(UserHandle user, String packageName,
                  ParceledListSlice shortcuts) {
              if (DEBUG) {
                  Log.d(TAG, "onShortcutChanged " + user.getIdentifier() + "," + packageName);
              }
              final List<ShortcutInfo> list = shortcuts.getList();
              synchronized (LauncherApps.this) {
                  for (CallbackMessageHandler callback : mCallbacks) {
                      callback.postOnShortcutChanged(packageName, user, list);
                  }
              }
          }
      };
  
      private static class CallbackMessageHandler extends Handler {
          private static final int MSG_ADDED = 1;
          private static final int MSG_REMOVED = 2;
          private static final int MSG_CHANGED = 3;
          private static final int MSG_AVAILABLE = 4;
          private static final int MSG_UNAVAILABLE = 5;
          private static final int MSG_SUSPENDED = 6;
          private static final int MSG_UNSUSPENDED = 7;
          private static final int MSG_SHORTCUT_CHANGED = 8;
  
          private LauncherApps.Callback mCallback;
  
          private static class CallbackInfo {
              String[] packageNames;
              String packageName;
              Bundle launcherExtras;
              boolean replacing;
              UserHandle user;
              List<ShortcutInfo> shortcuts;
          }
  
          public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) {
              super(looper, null, true);
              mCallback = callback;
          }
  
          @Override
          public void handleMessage(Message msg) {
              if (mCallback == null || !(msg.obj instanceof CallbackInfo)) {
                  return;
              }
              CallbackInfo info = (CallbackInfo) msg.obj;
              switch (msg.what) {
                  case MSG_ADDED:
                      mCallback.onPackageAdded(info.packageName, info.user);
                      break;
                  case MSG_REMOVED:
                      mCallback.onPackageRemoved(info.packageName, info.user);
                      break;
                  case MSG_CHANGED:
                      mCallback.onPackageChanged(info.packageName, info.user);
                      break;
                  case MSG_AVAILABLE:
                      mCallback.onPackagesAvailable(info.packageNames, info.user, info.replacing);
                      break;
                  case MSG_UNAVAILABLE:
                      mCallback.onPackagesUnavailable(info.packageNames, info.user, info.replacing);
                      break;
                  case MSG_SUSPENDED:
                      mCallback.onPackagesSuspended(info.packageNames, info.user, info.launcherExtras
                      );
                      break;
                  case MSG_UNSUSPENDED:
                      mCallback.onPackagesUnsuspended(info.packageNames, info.user);
                      break;
                  case MSG_SHORTCUT_CHANGED:
                      mCallback.onShortcutsChanged(info.packageName, info.shortcuts, info.user);
                      break;
              }
          }
  
          public void postOnPackageAdded(String packageName, UserHandle user) {
              CallbackInfo info = new CallbackInfo();
              info.packageName = packageName;
              info.user = user;
              obtainMessage(MSG_ADDED, info).sendToTarget();
          }
  
          public void postOnPackageRemoved(String packageName, UserHandle user) {
              CallbackInfo info = new CallbackInfo();
              info.packageName = packageName;
              info.user = user;
              obtainMessage(MSG_REMOVED, info).sendToTarget();
          }
  
          public void postOnPackageChanged(String packageName, UserHandle user) {
              CallbackInfo info = new CallbackInfo();
              info.packageName = packageName;
              info.user = user;
              obtainMessage(MSG_CHANGED, info).sendToTarget();
          }
  
          public void postOnPackagesAvailable(String[] packageNames, UserHandle user,
                  boolean replacing) {
              CallbackInfo info = new CallbackInfo();
              info.packageNames = packageNames;
              info.replacing = replacing;
              info.user = user;
              obtainMessage(MSG_AVAILABLE, info).sendToTarget();
          }
  
          public void postOnPackagesUnavailable(String[] packageNames, UserHandle user,
                  boolean replacing) {
              CallbackInfo info = new CallbackInfo();
              info.packageNames = packageNames;
              info.replacing = replacing;
              info.user = user;
              obtainMessage(MSG_UNAVAILABLE, info).sendToTarget();
          }
  
          public void postOnPackagesSuspended(String[] packageNames, Bundle launcherExtras,
                  UserHandle user) {
              CallbackInfo info = new CallbackInfo();
              info.packageNames = packageNames;
              info.user = user;
              info.launcherExtras = launcherExtras;
              obtainMessage(MSG_SUSPENDED, info).sendToTarget();
          }
  
          public void postOnPackagesUnsuspended(String[] packageNames, UserHandle user) {
              CallbackInfo info = new CallbackInfo();
              info.packageNames = packageNames;
              info.user = user;
              obtainMessage(MSG_UNSUSPENDED, info).sendToTarget();
          }
  
          public void postOnShortcutChanged(String packageName, UserHandle user,
                  List<ShortcutInfo> shortcuts) {
              CallbackInfo info = new CallbackInfo();
              info.packageName = packageName;
              info.user = user;
              info.shortcuts = shortcuts;
              obtainMessage(MSG_SHORTCUT_CHANGED, info).sendToTarget();
          }
      }
  
      /**
       * Register a callback to watch for session lifecycle events in this user and managed profiles.
       * @param callback The callback to register.
       * @param executor {@link Executor} to handle the callbacks, cannot be null.
       *
       * @see PackageInstaller#registerSessionCallback(SessionCallback)
       */
      public void registerPackageInstallerSessionCallback(
              @NonNull @CallbackExecutor Executor executor, @NonNull SessionCallback callback) {
          if (executor == null) {
              throw new NullPointerException("Executor must not be null");
          }
  
          synchronized (mDelegates) {
              final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback,
                      executor);
              try {
                  mService.registerPackageInstallerCallback(mContext.getPackageName(),
                          delegate);
              } catch (RemoteException e) {
                  throw e.rethrowFromSystemServer();
              }
              mDelegates.add(delegate);
          }
      }
  
      /**
       * Unregisters a callback that was previously registered.
       *
       * @param callback The callback to unregister.
       * @see #registerPackageInstallerSessionCallback(Executor, SessionCallback)
       */
      public void unregisterPackageInstallerSessionCallback(@NonNull SessionCallback callback) {
          synchronized (mDelegates) {
              for (Iterator<SessionCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) {
                  final SessionCallbackDelegate delegate = i.next();
                  if (delegate.mCallback == callback) {
                      mPm.getPackageInstaller().unregisterSessionCallback(delegate.mCallback);
                      i.remove();
                  }
              }
          }
      }
  
      /**
       * Return list of all known install sessions in this user and managed profiles, regardless
       * of the installer.
       *
       * @see PackageInstaller#getAllSessions()
       */
      public @NonNull List<SessionInfo> getAllPackageInstallerSessions() {
          try {
              return mService.getAllSessions(mContext.getPackageName()).getList();
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * A helper method to extract a {@link PinItemRequest} set to
       * the {@link #EXTRA_PIN_ITEM_REQUEST} extra.
       */
      public PinItemRequest getPinItemRequest(Intent intent) {
          return intent.getParcelableExtra(EXTRA_PIN_ITEM_REQUEST);
      }
  
      /**
       * Represents a "pin shortcut" or a "pin appwidget" request made by an app, which is sent with
       * an {@link #ACTION_CONFIRM_PIN_SHORTCUT} or {@link #ACTION_CONFIRM_PIN_APPWIDGET} intent
       * respectively to the default launcher app.
       *
       * <h3>Request of the {@link #REQUEST_TYPE_SHORTCUT} type.
       *
       * <p>A {@link #REQUEST_TYPE_SHORTCUT} request represents a request to pin a
       * {@link ShortcutInfo}.  If the launcher accepts a request, call {@link #accept()},
       * or {@link #accept(Bundle)} with a null or empty Bundle.  No options are defined for
       * pin-shortcuts requests.
       *
       * <p>{@link #getShortcutInfo()} always returns a non-null {@link ShortcutInfo} for this type.
       *
       * <p>The launcher may receive a request with a {@link ShortcutInfo} that is already pinned, in
       * which case {@link ShortcutInfo#isPinned()} returns true.  This means the user wants to create
       * another pinned shortcut for a shortcut that's already pinned.  If the launcher accepts it,
       * {@link #accept()} must still be called even though the shortcut is already pinned, and
       * create a new pinned shortcut icon for it.
       *
       * <p>See also {@link ShortcutManager} for more details.
       *
       * <h3>Request of the {@link #REQUEST_TYPE_APPWIDGET} type.
       *
       * <p>A {@link #REQUEST_TYPE_SHORTCUT} request represents a request to pin a
       * an AppWidget.  If the launcher accepts a request, call {@link #accept(Bundle)} with
       * the appwidget integer ID set to the
       * {@link android.appwidget.AppWidgetManager#EXTRA_APPWIDGET_ID} extra.
       *
       * <p>{@link #getAppWidgetProviderInfo(Context)} always returns a non-null
       * {@link AppWidgetProviderInfo} for this type.
       *
       * <p>See also {@link AppWidgetManager} for more details.
       *
       * @see #EXTRA_PIN_ITEM_REQUEST
       * @see #getPinItemRequest(Intent)
       */
      public static final class PinItemRequest implements Parcelable {
  
          /** This is a request to pin shortcut. */
          public static final int REQUEST_TYPE_SHORTCUT = 1;
  
          /** This is a request to pin app widget. */
          public static final int REQUEST_TYPE_APPWIDGET = 2;
  
          /** @hide */
          @IntDef(prefix = { "REQUEST_TYPE_" }, value = {
                  REQUEST_TYPE_SHORTCUT,
                  REQUEST_TYPE_APPWIDGET
          })
          @Retention(RetentionPolicy.SOURCE)
          public @interface RequestType {}
  
          private final int mRequestType;
          private final IPinItemRequest mInner;
  
          /**
           * @hide
           */
          public PinItemRequest(IPinItemRequest inner, int type) {
              mInner = inner;
              mRequestType = type;
          }
  
          /**
           * Represents the type of a request, which is one of the {@code REQUEST_TYPE_} constants.
           *
           * @return one of the {@code REQUEST_TYPE_} constants.
           */
          @RequestType
          public int getRequestType() {
              return mRequestType;
          }
  
          /**
           * {@link ShortcutInfo} sent by the requesting app.
           * Always non-null for a {@link #REQUEST_TYPE_SHORTCUT} request, and always null for a
           * different request type.
           *
           * @return requested {@link ShortcutInfo} when a request is of the
           * {@link #REQUEST_TYPE_SHORTCUT} type.  Null otherwise.
           */
          @Nullable
          public ShortcutInfo getShortcutInfo() {
              try {
                  return mInner.getShortcutInfo();
              } catch (RemoteException e) {
                  throw e.rethrowAsRuntimeException();
              }
          }
  
          /**
           * {@link AppWidgetProviderInfo} sent by the requesting app.
           * Always non-null for a {@link #REQUEST_TYPE_APPWIDGET} request, and always null for a
           * different request type.
           *
           * <p>Launcher should not show any configuration activity associated with the provider, and
           * assume that the widget is already fully configured. Upon accepting the widget, it should
           * pass the widgetId in {@link #accept(Bundle)}.
           *
           * @return requested {@link AppWidgetProviderInfo} when a request is of the
           * {@link #REQUEST_TYPE_APPWIDGET} type.  Null otherwise.
           */
          @Nullable
          public AppWidgetProviderInfo getAppWidgetProviderInfo(Context context) {
              try {
                  final AppWidgetProviderInfo info = mInner.getAppWidgetProviderInfo();
                  if (info == null) {
                      return null;
                  }
                  info.updateDimensions(context.getResources().getDisplayMetrics());
                  return info;
              } catch (RemoteException e) {
                  throw e.rethrowAsRuntimeException();
              }
          }
  
          /**
           * Any extras sent by the requesting app.
           *
           * @return For a shortcut request, this method always return null.  For an AppWidget
           * request, this method returns the extras passed to the
           * {@link android.appwidget.AppWidgetManager#requestPinAppWidget(
           * ComponentName, Bundle, PendingIntent)} API.  See {@link AppWidgetManager} for details.
           */
          @Nullable
          public Bundle getExtras() {
              try {
                  return mInner.getExtras();
              } catch (RemoteException e) {
                  throw e.rethrowAsRuntimeException();
              }
          }
  
          /**
           * Return whether a request is still valid.
           *
           * @return {@code TRUE} if a request is valid and {@link #accept(Bundle)} may be called.
           */
          public boolean isValid() {
              try {
                  return mInner.isValid();
              } catch (RemoteException e) {
                  return false;
              }
          }
  
          /**
           * Called by the receiving launcher app when the user accepts the request.
           *
           * @param options must be set for a {@link #REQUEST_TYPE_APPWIDGET} request.
           *
           * @return {@code TRUE} if the shortcut or the AppWidget has actually been pinned.
           * {@code FALSE} if the item hasn't been pinned, for example, because the request had
           * already been canceled, in which case the launcher must not pin the requested item.
           */
          public boolean accept(@Nullable Bundle options) {
              try {
                  return mInner.accept(options);
              } catch (RemoteException e) {
                  throw e.rethrowFromSystemServer();
              }
          }
  
          /**
           * Called by the receiving launcher app when the user accepts the request, with no options.
           *
           * @return {@code TRUE} if the shortcut or the AppWidget has actually been pinned.
           * {@code FALSE} if the item hasn't been pinned, for example, because the request had
           * already been canceled, in which case the launcher must not pin the requested item.
           */
          public boolean accept() {
              return accept(/* options= */ null);
          }
  
          private PinItemRequest(Parcel source) {
              final ClassLoader cl = getClass().getClassLoader();
  
              mRequestType = source.readInt();
              mInner = IPinItemRequest.Stub.asInterface(source.readStrongBinder());
          }
  
          @Override
          public void writeToParcel(Parcel dest, int flags) {
              dest.writeInt(mRequestType);
              dest.writeStrongBinder(mInner.asBinder());
          }
  
          public static final @android.annotation.NonNull Creator<PinItemRequest> CREATOR =
                  new Creator<PinItemRequest>() {
                      public PinItemRequest createFromParcel(Parcel source) {
                          return new PinItemRequest(source);
                      }
                      public PinItemRequest[] newArray(int size) {
                          return new PinItemRequest[size];
                      }
                  };
  
          @Override
          public int describeContents() {
              return 0;
          }
      }
  
      /**
       * A class that encapsulates information about the usage limit set for an app or
       * a group of apps.
       *
       * <p>The launcher can query specifics about the usage limit such as how much usage time
       * the limit has and how much of the total usage time is remaining via the APIs available
       * in this class.
       *
       * @see #getAppUsageLimit(String, UserHandle)
       * @hide
       */
      @SystemApi
      public static final class AppUsageLimit implements Parcelable {
          private final long mTotalUsageLimit;
          private final long mUsageRemaining;
  
          /** @hide */
          public AppUsageLimit(long totalUsageLimit, long usageRemaining) {
              this.mTotalUsageLimit = totalUsageLimit;
              this.mUsageRemaining = usageRemaining;
          }
  
          /**
           * Returns the total usage limit in milliseconds set for an app or a group of apps.
           *
           * @return the total usage limit in milliseconds
           */
          public long getTotalUsageLimit() {
              return mTotalUsageLimit;
          }
  
          /**
           * Returns the usage remaining in milliseconds for an app or the group of apps
           * this limit refers to.
           *
           * @return the usage remaining in milliseconds
           */
          public long getUsageRemaining() {
              return mUsageRemaining;
          }
  
          private AppUsageLimit(Parcel source) {
              mTotalUsageLimit = source.readLong();
              mUsageRemaining = source.readLong();
          }
  
          public static final @android.annotation.NonNull Creator<AppUsageLimit> CREATOR = new Creator<AppUsageLimit>() {
              @Override
              public AppUsageLimit createFromParcel(Parcel source) {
                  return new AppUsageLimit(source);
              }
  
              @Override
              public AppUsageLimit[] newArray(int size) {
                  return new AppUsageLimit[size];
              }
          };
  
          @Override
          public int describeContents() {
              return 0;
          }
  
          @Override
          public void writeToParcel(Parcel dest, int flags) {
              dest.writeLong(mTotalUsageLimit);
              dest.writeLong(mUsageRemaining);
          }
      }
  }
  