/* * 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 static android.Manifest.permission; import static android.Manifest.permission.ACCESS_HIDDEN_PROFILES; import static android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL; import static android.Manifest.permission.READ_FRAME_BUFFER; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.compat.annotation.UnsupportedAppUsage; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.LocusId; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionCallbackDelegate; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager.ApplicationInfoFlagsBits; 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.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Flags; 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.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; import android.window.IDumpCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; 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. *

* 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}. *

* 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. * *

See the {@link ShortcutManager} javadoc for details. * *

* 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. * *

See the {@link android.appwidget.AppWidgetManager#requestPinAppWidget} javadoc for * details. * *

* 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} & {@link #ACTION_CONFIRM_PIN_APPWIDGET} * containing a {@link PinItemRequest} of appropriate type asked to pin. * *

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"; /** * Cache shortcuts which are used in notifications. * @hide */ public static final int FLAG_CACHE_NOTIFICATION_SHORTCUTS = 0; /** * Cache shortcuts which are used in bubbles. * @hide */ public static final int FLAG_CACHE_BUBBLE_SHORTCUTS = 1; /** * Cache shortcuts which are used in People Tile. * @hide */ public static final int FLAG_CACHE_PEOPLE_TILE_SHORTCUTS = 2; /** @hide */ @IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = { FLAG_CACHE_NOTIFICATION_SHORTCUTS, FLAG_CACHE_BUBBLE_SHORTCUTS, FLAG_CACHE_PEOPLE_TILE_SHORTCUTS }) @Retention(RetentionPolicy.SOURCE) public @interface ShortcutCacheFlags {} 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 mCallbacks = new ArrayList<>(); private final List mDelegates = new ArrayList<>(); private final Map> mShortcutChangeCallbacks = new HashMap<>(); /** * 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. * *

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. * *

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) * @deprecated {@code launcherExtras} should be obtained by using * {@link #getSuspendedPackageLauncherExtras(String, UserHandle)}. For all other cases, * {@link #onPackagesSuspended(String[], UserHandle)} should be used. */ @Deprecated 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. * *

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 shortcuts, @NonNull UserHandle user) { } /** * Indicates that the loading progress of an installed package has changed. * * @param packageName The name of the package that has changed. * @param user The UserHandle of the profile that generated the change. * @param progress The new progress value, between [0, 1]. */ public void onPackageLoadingProgressChanged(@NonNull String packageName, @NonNull UserHandle user, float progress) {} } /** * 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. * *

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. * *

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; /** * Include cached shortcuts in the result. */ public static final int FLAG_MATCH_CACHED = 1 << 4; /** @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. * *

The caller must be the selected assistant app to use this flag, or have the system * {@code ACCESS_SHORTCUTS} permission. * *

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. * *

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 | FLAG_MATCH_CACHED * @hide */ public static final int FLAG_MATCH_ALL_KINDS = FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_CACHED; /** * 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. * *

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; /** * Includes shortcuts from persistence layer in the search result. * *

The caller should make the query on a worker thread since accessing persistence layer * is considered asynchronous. * * @hide */ @SystemApi public static final int FLAG_GET_PERSISTED_DATA = 1 << 12; /** * Populate the persons field in the result. See {@link ShortcutInfo#getPersons()}. * *

The caller must have the system {@code ACCESS_SHORTCUTS} permission. * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 1 << 11; /** @hide */ @IntDef(flag = true, prefix = { "FLAG_" }, value = { FLAG_MATCH_DYNAMIC, FLAG_MATCH_PINNED, FLAG_MATCH_MANIFEST, FLAG_MATCH_CACHED, FLAG_MATCH_PINNED_BY_ANY_LAUNCHER, FLAG_GET_KEY_FIELDS_ONLY, FLAG_GET_PERSONS_DATA, FLAG_GET_PERSISTED_DATA }) @Retention(RetentionPolicy.SOURCE) public @interface QueryFlags {} long mChangedSince; @Nullable String mPackage; @Nullable List mShortcutIds; @Nullable List mLocusIds; @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—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 shortcutIds) { mShortcutIds = shortcutIds; return this; } /** * If non-null, return only the specified shortcuts by locus ID. When setting this field, * a package name must also be set with {@link #setPackage}. */ @NonNull public ShortcutQuery setLocusIds(@Nullable List locusIds) { mLocusIds = locusIds; 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. * *

*/ public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) { mQueryFlags = queryFlags; return this; } } /** * Callbacks for shortcut changes to this and related managed profiles. * * @hide */ public interface ShortcutChangeCallback { /** * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to * register this callback, have been added or updated. * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery, * Executor) * *

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 Shortcuts from the package that have updated or added. 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 */ default void onShortcutsAddedOrUpdated(@NonNull String packageName, @NonNull List shortcuts, @NonNull UserHandle user) {} /** * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to * register this callback, have been removed. * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery, * Executor) * *

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 Shortcuts from the package that have been removed. 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 */ default void onShortcutsRemoved(@NonNull String packageName, @NonNull List shortcuts, @NonNull UserHandle user) {} } /** * Callback proxy class for {@link ShortcutChangeCallback} * * @hide */ private static class ShortcutChangeCallbackProxy extends android.content.pm.IShortcutChangeCallback.Stub { private final WeakReference> mRemoteReferences; ShortcutChangeCallbackProxy(Executor executor, ShortcutChangeCallback callback) { mRemoteReferences = new WeakReference<>(new Pair<>(executor, callback)); } @Override public void onShortcutsAddedOrUpdated(@NonNull String packageName, @NonNull List shortcuts, @NonNull UserHandle user) { Pair remoteReferences = mRemoteReferences.get(); if (remoteReferences == null) { // Binder is dead. return; } final Executor executor = remoteReferences.first; final ShortcutChangeCallback callback = remoteReferences.second; executor.execute( PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsAddedOrUpdated, callback, packageName, shortcuts, user).recycleOnUse()); } @Override public void onShortcutsRemoved(@NonNull String packageName, @NonNull List shortcuts, @NonNull UserHandle user) { Pair remoteReferences = mRemoteReferences.get(); if (remoteReferences == null) { // Binder is dead. return; } final Executor executor = remoteReferences.first; final ShortcutChangeCallback callback = remoteReferences.second; executor.execute( PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsRemoved, callback, packageName, shortcuts, user).recycleOnUse()); } } /** @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, the target * user is different from the calling user, and it is not called from a package that has the * {@link permission.INTERACT_ACROSS_USERS_FULL} permission, in order to help * developers to detect it. */ private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) { if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile() && mContext.checkSelfPermission(permission.INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED) { 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. * *

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. * *

To get hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

* */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public List getProfiles() { if (mUserManager.isManagedProfile() || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks() && android.os.Flags.allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures() && mUserManager.isPrivateProfile())) { // If it's a managed or private profile, only return the current profile. final List result = new ArrayList(1); result.add(android.os.Process.myUserHandle()); return result; } else { if (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()) { try { return mService.getUserProfiles(); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } 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 ACTION_MAIN or CATEGORY_LAUNCHER, * the system adds a synthesized activity to the list. This synthesized activity represents the * app's details page within system settings. * *

Note: It's possible for system apps, such as app stores, to prevent * the system from adding synthesized activities to the returned list.

* *

As of Android Q, 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:

* * *

Additionally, the system hides synthesized activities for some or all apps in the * following enterprise-related cases:

* * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

* * * @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. */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public List getActivityList(String packageName, UserHandle user) { logErrorForInvalidProfileAccess(user); try { return convertToActivityList(mService.getLauncherActivities(mContext.getPackageName(), packageName, user), user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Returns a mutable PendingIntent that would start the same activity started from * {@link #startMainActivity(ComponentName, UserHandle, Rect, Bundle)}. The caller needs to * take care in ensuring that the mutable intent returned is not passed to untrusted parties. * * @param component The ComponentName of the activity to launch * @param startActivityOptions This parameter is no longer supported * @param user The UserHandle of the profile * @hide */ @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) @Nullable public PendingIntent getMainActivityLaunchIntent(@NonNull ComponentName component, @Nullable Bundle startActivityOptions, @NonNull UserHandle user) { logErrorForInvalidProfileAccess(user); if (DEBUG) { Log.i(TAG, "GetMainActivityLaunchIntent " + component + " " + user); } try { return mService.getActivityLaunchIntent(mContext.getPackageName(), component, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Returns information related to a user which is useful for displaying UI elements * to distinguish it from other users (eg, badges). * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

* * * @param userHandle user handle of the user for which LauncherUserInfo is requested. * @return the {@link LauncherUserInfo} object related to the user specified, null in case * the user is inaccessible. */ @Nullable @SuppressLint("RequiresPermission") @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) { if (DEBUG) { Log.i(TAG, "getLauncherUserInfo " + userHandle); } try { return mService.getLauncherUserInfo(userHandle); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Returns an intent sender which can be used to start the App Market activity (Installer * Activity). * This method is primarily used to get an intent sender which starts App Market activity for * another profile, if the caller is not otherwise allowed to start activity in that profile. * *

When packageName is set, intent sender to start the App Market Activity which installed * the package in calling user will be returned, but for the profile passed. * *

When packageName is not set, intent sender to launch the default App Market Activity for * the profile will be returned. In case there are multiple App Market Activities available for * the profile, IntentPicker will be started, allowing user to choose the preferred activity. * *

The method will fall back to the behaviour of not having the packageName set, in case: *

*

* *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

* * * @param packageName the package for which intent sender to launch App Market Activity is * required. * @param user the profile for which intent sender to launch App Market Activity is required. * @return {@link IntentSender} object which launches the App Market Activity, null in case * there is no such activity. */ @Nullable @SuppressLint("RequiresPermission") @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public IntentSender getAppMarketActivityIntent(@Nullable String packageName, @NonNull UserHandle user) { if (DEBUG) { Log.i(TAG, "getAppMarketActivityIntent for package: " + packageName + " user: " + user); } try { return mService.getAppMarketActivityIntent(mContext.getPackageName(), packageName, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Returns the list of the system packages that are installed at user creation. * *

An empty list denotes that all system packages should be treated as pre-installed for that * user at creation. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

* * * @param userHandle the user for which installed system packages are required. * @return {@link List} of {@link String}, representing the package name of the installed * package. Can be empty but not null. */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) @NonNull @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public List getPreInstalledSystemPackages(@NonNull UserHandle userHandle) { if (DEBUG) { Log.i(TAG, "getPreInstalledSystemPackages for user: " + userHandle); } try { return mService.getPreInstalledSystemPackages(userHandle); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Returns {@link IntentSender} which can be used to start the Private Space Settings Activity. * *

Caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the * permissions required.

* * @return {@link IntentSender} object which launches the Private Space Settings Activity, if * successful, null otherwise. * @hide */ @Nullable @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public IntentSender getPrivateSpaceSettingsIntent() { try { return mService.getPrivateSpaceSettingsIntent(); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it * returns null. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @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. */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) { logErrorForInvalidProfileAccess(user); try { LauncherActivityInfoInternal ai = mService.resolveLauncherActivityInternal( mContext.getPackageName(), intent.getComponent(), user); if (ai == null) { return null; } return new LauncherActivityInfo(mContext, ai); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Returns overrides for the activities that should be launched for the shortcuts of certain * package names. * * @return {@link Map} whose keys are package names and whose values are the * {@link LauncherActivityInfo}s that should be used for those packages' shortcuts. If there are * no activity overrides, an empty {@link Map} will be returned. * * @hide */ @NonNull public Map getActivityOverrides() { Map activityOverrides = new ArrayMap<>(); try { Map activityOverridesInternal = mService.getActivityOverrides(mContext.getPackageName(), mContext.getUserId()); for (Map.Entry packageToOverride : activityOverridesInternal.entrySet()) { activityOverrides.put( packageToOverride.getKey(), new LauncherActivityInfo( mContext, packageToOverride.getValue() ) ); } } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } return activityOverrides; } /** * Starts a Main activity in the specified profile. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @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 */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) 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(), mContext.getAttributionTag(), 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(), mContext.getAttributionTag(), 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. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @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 */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public void startAppDetailsActivity(ComponentName component, UserHandle user, Rect sourceBounds, Bundle opts) { logErrorForInvalidProfileAccess(user); try { mService.showAppDetailsAsUser(mContext.getIApplicationThread(), mContext.getPackageName(), mContext.getAttributionTag(), component, sourceBounds, opts, user); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** * Returns PendingIntent associated with specified shortcut. * * @param packageName The packageName of the shortcut * @param shortcutId The id of the shortcut * @param opts This parameter is no longer supported * @param user The UserHandle of the profile */ @Nullable public PendingIntent getShortcutIntent(@NonNull final String packageName, @NonNull final String shortcutId, @Nullable final Bundle opts, @NonNull final UserHandle user) { logErrorForInvalidProfileAccess(user); if (DEBUG) { Log.i(TAG, "GetShortcutIntent " + packageName + "/" + shortcutId + " " + user); } try { // due to b/209607104, opts will be ignored return mService.getShortcutIntent( mContext.getPackageName(), packageName, shortcutId, null /* 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. Empty list will * be returned for user-profiles that have items restricted on home screen. * * @see Intent#ACTION_CREATE_SHORTCUT * @see #getShortcutConfigActivityIntent(LauncherActivityInfo) */ public List 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 convertToActivityList( @Nullable ParceledListSlice internals, UserHandle user) { if (internals == null || internals.getList().isEmpty()) { return Collections.EMPTY_LIST; } ArrayList lais = new ArrayList<>(); for (LauncherActivityInfoInternal internal : internals.getList()) { LauncherActivityInfo lai = new LauncherActivityInfo(mContext, internal); 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. * *

The caller should receive {@link PinItemRequest} in onActivityResult on * {@link android.app.Activity#RESULT_OK}. * *

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. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @param packageName The package to check. * @param user The UserHandle of the profile. * * @return true if the package exists and is enabled. */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) 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)}. * *

The contents of this {@link Bundle} are supposed to be a contract between the suspending * app and the launcher. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* *

Note: This just returns whatever extras were provided to the system, which might * even be {@code null}. * * @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() */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) 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)}. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @param packageName The package for which to check. * @param user the {@link UserHandle} of the profile. * @return */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public boolean shouldHideFromSuggestions(@NonNull String packageName, @NonNull UserHandle user) { Objects.requireNonNull(packageName, "packageName"); Objects.requireNonNull(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. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @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. */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public ApplicationInfo getApplicationInfo(@NonNull String packageName, @ApplicationInfoFlagsBits int flags, @NonNull UserHandle user) throws PackageManager.NameNotFoundException { Objects.requireNonNull(packageName, "packageName"); Objects.requireNonNull(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. * *

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. * *

If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @param component The activity to check. * @param user The UserHandle of the profile. * * @return true if the activity exists and is enabled. */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) 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: * *
    *
  • The current launcher (or default launcher if there is no set current launcher).
  • *
  • The currently active voice interaction service.
  • *
* *

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 not 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 maybeUpdateDisabledMessage(List 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; } /** * Register a callback to be called right before the wmtrace data is moved to the bugreport. * @hide */ @RequiresPermission(READ_FRAME_BUFFER) public void registerDumpCallback(IDumpCallback cb) { try { mService.registerDumpCallback(cb); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } } /** * Saves view capture data to the default location. * @hide */ @RequiresPermission(READ_FRAME_BUFFER) public void saveViewCaptureData() { try { mService.saveViewCaptureData(); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } } /** * Unregister a callback, so that it won't be called when LauncherApps dumps. * @hide */ @RequiresPermission(READ_FRAME_BUFFER) public void unRegisterDumpCallback(IDumpCallback cb) { try { mService.unRegisterDumpCallback(cb); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } } /** * Returns {@link ShortcutInfo}s that match {@code query}. * *

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 getShortcuts(@NonNull ShortcutQuery query, @NonNull UserHandle user) { logErrorForInvalidProfileAccess(user); try { if ((query.mQueryFlags & ShortcutQuery.FLAG_GET_PERSISTED_DATA) != 0) { return getShortcutsBlocked(query, user); } // 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(), new ShortcutQueryWrapper(query), user) .getList()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private List getShortcutsBlocked(@NonNull ShortcutQuery query, @NonNull UserHandle user) { logErrorForInvalidProfileAccess(user); final AndroidFuture> future = new AndroidFuture<>(); future.thenApply(this::maybeUpdateDisabledMessage); try { mService.getShortcutsAsync(mContext.getPackageName(), new ShortcutQueryWrapper(query), user, future); return future.get(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } /** * @hide // No longer used. Use getShortcuts() instead. Kept for unit tests. */ @Nullable @Deprecated public List getShortcutInfo(@NonNull String packageName, @NonNull List 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. * *

This API is NOT cumulative; this will replace all pinned shortcuts for the package. * However, different launchers may have different set of pinned shortcuts. * *

The calling launcher application must be allowed to access the shortcut information, * as defined in {@link #hasShortcutHostPermission()}. * *

For user-profiles with items restricted on home screen, caller must have the required * permission. * * @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 */ @RequiresPermission(conditional = true, value = android.Manifest.permission.ACCESS_SHORTCUTS) public void pinShortcuts(@NonNull String packageName, @NonNull List shortcutIds, @NonNull UserHandle user) { logErrorForInvalidProfileAccess(user); try { mService.pinShortcuts(mContext.getPackageName(), packageName, shortcutIds, user); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Mark shortcuts as cached for a package. * *

Only dynamic long lived shortcuts can be cached. None dynamic or non long lived shortcuts * in the list will be ignored. * *

Unlike pinned shortcuts, where different callers can have different sets of pinned * shortcuts, cached state is per shortcut only, and even if multiple callers cache the same * shortcut, it can be uncached by any valid caller. * * @param packageName The target package name. * @param shortcutIds The IDs of the shortcut to be cached. * @param user The UserHandle of the profile. * @param cacheFlags One of the values in: *

    *
  • {@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS} *
  • {@link #FLAG_CACHE_BUBBLE_SHORTCUTS} *
  • {@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS} *
* @throws IllegalStateException when the user is locked, or when the {@code user} user * is locked or not running. * * @see ShortcutManager * * @hide */ @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public void cacheShortcuts(@NonNull String packageName, @NonNull List shortcutIds, @NonNull UserHandle user, @ShortcutCacheFlags int cacheFlags) { logErrorForInvalidProfileAccess(user); try { mService.cacheShortcuts( mContext.getPackageName(), packageName, shortcutIds, user, cacheFlags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Remove cached flag from shortcuts for a package. * * @param packageName The target package name. * @param shortcutIds The IDs of the shortcut to be uncached. * @param user The UserHandle of the profile. * @param cacheFlags One of the values in: *
    *
  • {@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS} *
  • {@link #FLAG_CACHE_BUBBLE_SHORTCUTS} *
  • {@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS} *
* @throws IllegalStateException when the user is locked, or when the {@code user} user * is locked or not running. * * @see ShortcutManager * * @hide */ @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public void uncacheShortcuts(@NonNull String packageName, @NonNull List shortcutIds, @NonNull UserHandle user, @ShortcutCacheFlags int cacheFlags) { logErrorForInvalidProfileAccess(user); try { mService.uncacheShortcuts( mContext.getPackageName(), packageName, shortcutIds, user, cacheFlags); } 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 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(); } } /** * @hide internal/unit tests only */ @VisibleForTesting public ParcelFileDescriptor getUriShortcutIconFd(@NonNull ShortcutInfo shortcut) { return getUriShortcutIconFd(shortcut.getPackage(), shortcut.getId(), shortcut.getUserId()); } private ParcelFileDescriptor getUriShortcutIconFd(@NonNull String packageName, @NonNull String shortcutId, int userId) { String uri = getShortcutIconUri(packageName, shortcutId, userId); if (uri == null) { return null; } try { return mContext.getContentResolver().openFileDescriptor(Uri.parse(uri), "r"); } catch (Exception e) { Log.e(TAG, "Failed to open icon file: " + uri, e); return null; } } private String getShortcutIconUri(@NonNull String packageName, @NonNull String shortcutId, int userId) { String uri = null; try { uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return uri; } /** * Returns the icon for this shortcut, without any badging for the profile. * *

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); return loadDrawableFromFileDescriptor(pfd, shortcut.hasAdaptiveBitmap()); } else if (shortcut.hasIconUri()) { final ParcelFileDescriptor pfd = getUriShortcutIconFd(shortcut); return loadDrawableFromFileDescriptor(pfd, shortcut.hasAdaptiveBitmap()); } 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 loadDrawableFromFileDescriptor(ParcelFileDescriptor pfd, boolean adaptive) { if (pfd == null) { return null; } try { final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); if (bmp != null) { BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp); if (adaptive) { return new AdaptiveIconDrawable(null, dr); } else { return dr; } } return null; } finally { try { pfd.close(); } catch (IOException ignore) { } } } /** * @hide */ public Icon getShortcutIcon(@NonNull ShortcutInfo shortcut) { if (shortcut.hasIconFile()) { final ParcelFileDescriptor pfd = getShortcutIconFd(shortcut); if (pfd == null) { return null; } try { final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); if (bmp != null) { if (shortcut.hasAdaptiveBitmap()) { return Icon.createWithAdaptiveBitmap(bmp); } else { return Icon.createWithBitmap(bmp); } } return null; } finally { try { pfd.close(); } catch (IOException ignore) { } } } else if (shortcut.hasIconUri()) { String uri = getShortcutIconUri(shortcut.getPackage(), shortcut.getId(), shortcut.getUserId()); if (uri == null) { return null; } if (shortcut.hasAdaptiveBitmap()) { return Icon.createWithAdaptiveBitmapContentUri(uri); } else { return Icon.createWithContentUri(uri); } } else if (shortcut.hasIconResource()) { return Icon.createWithResource(shortcut.getPackage(), shortcut.getIconResourceId()); } else { return shortcut.getIcon(); } } 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. * *

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. * *

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. * *

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, null /* default featureId */, 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. * *

To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @param callback The callback to register. */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public void registerCallback(Callback callback) { registerCallback(callback, null); } /** * Registers a callback for changes to packages in this user and managed profiles. * *

To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @param callback The callback to register. * @param handler that should be used to post callbacks on, may be null. */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) 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(); } } } } /** * Disable different archive compatibility options of the launcher for the caller of this * method. * * @see ArchiveCompatibilityParams for individual options. */ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public void setArchiveCompatibility(@NonNull ArchiveCompatibilityParams params) { try { mService.setArchiveCompatibilityOptions(params.isEnableIconOverlay(), params.isEnableUnarchivalConfirmation()); } 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 final 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() + "," + Arrays.toString(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() + "," + Arrays.toString(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() + "," + Arrays.toString(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() + "," + Arrays.toString(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 list = shortcuts.getList(); synchronized (LauncherApps.this) { for (CallbackMessageHandler callback : mCallbacks) { callback.postOnShortcutChanged(packageName, user, list); } } } public void onPackageLoadingProgressChanged(UserHandle user, String packageName, float progress) { if (DEBUG) { Log.d(TAG, "onPackageLoadingProgressChanged " + user.getIdentifier() + "," + packageName + "," + progress); } synchronized (LauncherApps.this) { for (CallbackMessageHandler callback : mCallbacks) { callback.postOnPackageLoadingProgressChanged(user, packageName, progress); } } } }; /** * Used to enable Archiving compatibility options with {@link #setArchiveCompatibility}. */ @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) public static class ArchiveCompatibilityParams { private boolean mEnableIconOverlay = true; private boolean mEnableUnarchivalConfirmation = true; /** @hide */ public boolean isEnableIconOverlay() { return mEnableIconOverlay; } /** @hide */ public boolean isEnableUnarchivalConfirmation() { return mEnableUnarchivalConfirmation; } /** * If true, provides a cloud overlay for archived apps to ensure users are aware that a * certain app is archived. True by default. * *

Launchers might want to disable this operation if they want to provide custom user * experience to differentiate archived apps. */ public void setEnableIconOverlay(boolean enableIconOverlay) { this.mEnableIconOverlay = enableIconOverlay; } /** * If true, the user is shown a confirmation dialog when they click an archived app, which * explains that the app will be downloaded and restored in the background. True by default. * *

Launchers might want to disable this operation if they provide sufficient, * alternative user guidance to highlight that an unarchival is starting and ongoing once an * archived app is tapped. E.g., this could be achieved by showing the unarchival progress * around the icon. */ public void setEnableUnarchivalConfirmation(boolean enableUnarchivalConfirmation) { this.mEnableUnarchivalConfirmation = enableUnarchivalConfirmation; } } 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 static final int MSG_LOADING_PROGRESS_CHANGED = 9; private final LauncherApps.Callback mCallback; private static class CallbackInfo { String[] packageNames; String packageName; Bundle launcherExtras; boolean replacing; UserHandle user; List shortcuts; float mLoadingProgress; } 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; case MSG_LOADING_PROGRESS_CHANGED: mCallback.onPackageLoadingProgressChanged(info.packageName, info.user, info.mLoadingProgress); 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 shortcuts) { CallbackInfo info = new CallbackInfo(); info.packageName = packageName; info.user = user; info.shortcuts = shortcuts; obtainMessage(MSG_SHORTCUT_CHANGED, info).sendToTarget(); } public void postOnPackageLoadingProgressChanged(UserHandle user, String packageName, float progress) { CallbackInfo info = new CallbackInfo(); info.packageName = packageName; info.user = user; info.mLoadingProgress = progress; obtainMessage(MSG_LOADING_PROGRESS_CHANGED, info).sendToTarget(); } } /** * Register a callback to watch for session lifecycle events in this user and managed profiles. * Callers need to either declare <queries> element with the specific package name in the * app's manifest, have the android.permission.QUERY_ALL_PACKAGES, or be the session owner to * watch for these events. * *

Session callbacks are not sent for user-profiles that have items restricted on home * screen. * * @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 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. Callers need to either declare <queries> element with the specific * package name in the app's manifest, have the android.permission.QUERY_ALL_PACKAGES, or be * the session owner to retrieve these details. * *

To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, * caller should have either:

*
    *
  • the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} * permission
  • *
  • the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the * {@link android.app.role.RoleManager.ROLE_HOME} role.
  • *
* * @see PackageInstaller#getAllSessions() */ @SuppressLint("RequiresPermission") @RequiresPermission(conditional = true, anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public @NonNull List getAllPackageInstallerSessions() { try { return mService.getAllSessions(mContext.getPackageName()).getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Register a callback to watch for shortcut change events in this user and managed profiles. * * @param callback The callback to register. * @param query {@link ShortcutQuery} to match and filter the shortcut events. Only matching * shortcuts will be returned by the callback. * @param executor {@link Executor} to handle the callbacks. To dispatch callbacks to the main * thread of your application, you can use {@link android.content.Context#getMainExecutor()}. * * @hide */ public void registerShortcutChangeCallback(@NonNull ShortcutChangeCallback callback, @NonNull ShortcutQuery query, @NonNull @CallbackExecutor Executor executor) { Objects.requireNonNull(callback, "Callback cannot be null"); Objects.requireNonNull(query, "Query cannot be null"); Objects.requireNonNull(executor, "Executor cannot be null"); synchronized (mShortcutChangeCallbacks) { IShortcutChangeCallback proxy = new ShortcutChangeCallbackProxy(executor, callback); mShortcutChangeCallbacks.put(callback, new Pair<>(executor, proxy)); try { mService.registerShortcutChangeCallback(mContext.getPackageName(), new ShortcutQueryWrapper(query), proxy); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Unregisters a callback that was previously registered. * @see #registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery, Executor) * * @param callback Callback to be unregistered. * * @hide */ public void unregisterShortcutChangeCallback(@NonNull ShortcutChangeCallback callback) { Objects.requireNonNull(callback, "Callback cannot be null"); synchronized (mShortcutChangeCallbacks) { if (mShortcutChangeCallbacks.containsKey(callback)) { IShortcutChangeCallback proxy = mShortcutChangeCallbacks.remove(callback).second; try { mService.unregisterShortcutChangeCallback(mContext.getPackageName(), proxy); } 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, android.content.pm.LauncherApps.PinItemRequest.class); } /** * 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. * *

Request of the {@link #REQUEST_TYPE_SHORTCUT} type.

* *

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. * *

{@link #getShortcutInfo()} always returns a non-null {@link ShortcutInfo} for this type. * *

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. * *

See also {@link ShortcutManager} for more details. * *

Request of the {@link #REQUEST_TYPE_APPWIDGET} type.

* *

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. * *

{@link #getAppWidgetProviderInfo(Context)} always returns a non-null * {@link AppWidgetProviderInfo} for this type. * *

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. * *

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 CREATOR = new Creator() { 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. * *

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 CREATOR = new Creator() { @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); } } }