2805 lines
114 KiB
Java
2805 lines
114 KiB
Java
![]() |
/*
|
||
|
* 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.
|
||
|
* <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} & {@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";
|
||
|
|
||
|
/**
|
||
|
* 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<CallbackMessageHandler> mCallbacks = new ArrayList<>();
|
||
|
private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>();
|
||
|
|
||
|
private final Map<ShortcutChangeCallback, Pair<Executor, IShortcutChangeCallback>>
|
||
|
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.
|
||
|
*
|
||
|
* <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)
|
||
|
* @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.
|
||
|
*
|
||
|
* <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) {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*
|
||
|
* <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;
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*
|
||
|
* <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 | 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.
|
||
|
*
|
||
|
* <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;
|
||
|
|
||
|
/**
|
||
|
* Includes shortcuts from persistence layer in the search result.
|
||
|
*
|
||
|
* <p>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()}.
|
||
|
*
|
||
|
* <p>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<String> mShortcutIds;
|
||
|
|
||
|
@Nullable
|
||
|
List<LocusId> 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<String> 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<LocusId> 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.
|
||
|
*
|
||
|
* <ul>
|
||
|
* <li>{@link #FLAG_MATCH_DYNAMIC}
|
||
|
* <li>{@link #FLAG_MATCH_PINNED}
|
||
|
* <li>{@link #FLAG_MATCH_MANIFEST}
|
||
|
* <li>{@link #FLAG_MATCH_CACHED}
|
||
|
* <li>{@link #FLAG_GET_KEY_FIELDS_ONLY}
|
||
|
* </ul>
|
||
|
*/
|
||
|
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)
|
||
|
*
|
||
|
* <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 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<ShortcutInfo> 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)
|
||
|
*
|
||
|
* <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 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<ShortcutInfo> shortcuts, @NonNull UserHandle user) {}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Callback proxy class for {@link ShortcutChangeCallback}
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
private static class ShortcutChangeCallbackProxy extends
|
||
|
android.content.pm.IShortcutChangeCallback.Stub {
|
||
|
private final WeakReference<Pair<Executor, ShortcutChangeCallback>> mRemoteReferences;
|
||
|
|
||
|
ShortcutChangeCallbackProxy(Executor executor, ShortcutChangeCallback callback) {
|
||
|
mRemoteReferences = new WeakReference<>(new Pair<>(executor, callback));
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onShortcutsAddedOrUpdated(@NonNull String packageName,
|
||
|
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
|
||
|
Pair<Executor, ShortcutChangeCallback> 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<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
|
||
|
Pair<Executor, ShortcutChangeCallback> 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.
|
||
|
*
|
||
|
* <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.
|
||
|
*
|
||
|
* <p>To get hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*/
|
||
|
@SuppressLint("RequiresPermission")
|
||
|
@RequiresPermission(conditional = true,
|
||
|
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
|
||
|
public List<UserHandle> 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 <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>
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </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.
|
||
|
*/
|
||
|
@SuppressLint("RequiresPermission")
|
||
|
@RequiresPermission(conditional = true,
|
||
|
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
|
||
|
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 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).
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* <p>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.
|
||
|
*
|
||
|
* <p>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.
|
||
|
*
|
||
|
* <p>The method will fall back to the behaviour of not having the packageName set, in case:
|
||
|
* <ul>
|
||
|
* <li>No activity for the packageName is found in calling user-space.</li>
|
||
|
* <li>The App Market Activity which installed the package in calling user-space is not
|
||
|
* present.</li>
|
||
|
* <li>The App Market Activity which installed the package in calling user-space is not
|
||
|
* present in the profile passed.</li>
|
||
|
* </ul>
|
||
|
* </p>
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* <p>An empty list denotes that all system packages should be treated as pre-installed for that
|
||
|
* user at creation.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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<String> 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.
|
||
|
*
|
||
|
* <p> Caller should have {@link android.app.role.RoleManager.ROLE_HOME} and either of the
|
||
|
* permissions required.</p>
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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<String, LauncherActivityInfo> getActivityOverrides() {
|
||
|
Map<String, LauncherActivityInfo> activityOverrides = new ArrayMap<>();
|
||
|
try {
|
||
|
Map<String, LauncherActivityInfoInternal> activityOverridesInternal =
|
||
|
mService.getActivityOverrides(mContext.getPackageName(), mContext.getUserId());
|
||
|
for (Map.Entry<String, LauncherActivityInfoInternal> 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.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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<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<LauncherActivityInfoInternal> internals, UserHandle user) {
|
||
|
if (internals == null || internals.getList().isEmpty()) {
|
||
|
return Collections.EMPTY_LIST;
|
||
|
}
|
||
|
ArrayList<LauncherActivityInfo> 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.
|
||
|
*
|
||
|
* <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.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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)}.
|
||
|
*
|
||
|
* <p>The contents of this {@link Bundle} are supposed to be a contract between the suspending
|
||
|
* app and the launcher.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* <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()
|
||
|
*/
|
||
|
@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)}.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* <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.
|
||
|
*
|
||
|
* <p>If the user in question is a hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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:
|
||
|
*
|
||
|
* <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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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}.
|
||
|
*
|
||
|
* <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 {
|
||
|
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<ShortcutInfo> getShortcutsBlocked(@NonNull ShortcutQuery query,
|
||
|
@NonNull UserHandle user) {
|
||
|
logErrorForInvalidProfileAccess(user);
|
||
|
final AndroidFuture<List<ShortcutInfo>> 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<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()}.
|
||
|
*
|
||
|
* <p>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<String> 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.
|
||
|
*
|
||
|
* <p>Only dynamic long lived shortcuts can be cached. None dynamic or non long lived shortcuts
|
||
|
* in the list will be ignored.
|
||
|
*
|
||
|
* <p>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:
|
||
|
* <ul>
|
||
|
* <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
|
||
|
* <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
|
||
|
* <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
|
||
|
* </ul>
|
||
|
* @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<String> 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:
|
||
|
* <ul>
|
||
|
* <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
|
||
|
* <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
|
||
|
* <li>{@link #FLAG_CACHE_PEOPLE_TILE_SHORTCUTS}
|
||
|
* </ul>
|
||
|
* @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<String> 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<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();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*
|
||
|
* <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);
|
||
|
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.
|
||
|
*
|
||
|
* <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,
|
||
|
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.
|
||
|
*
|
||
|
* <p>To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* <p>To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @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<ShortcutInfo> 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.
|
||
|
*
|
||
|
* <p> 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.
|
||
|
*
|
||
|
* <p> 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<ShortcutInfo> 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<ShortcutInfo> 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.
|
||
|
*
|
||
|
* <p> 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<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. 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.
|
||
|
*
|
||
|
* <p>To receive callbacks for hidden profile {@link UserManager.USER_TYPE_PROFILE_PRIVATE},
|
||
|
* caller should have either:</p>
|
||
|
* <ul>
|
||
|
* <li>the privileged {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES}
|
||
|
* permission</li>
|
||
|
* <li>the normal {@link android.Manifest.permission.ACCESS_HIDDEN_PROFILES} permission and the
|
||
|
* {@link android.app.role.RoleManager.ROLE_HOME} role. </li>
|
||
|
* </ul>
|
||
|
*
|
||
|
* @see PackageInstaller#getAllSessions()
|
||
|
*/
|
||
|
@SuppressLint("RequiresPermission")
|
||
|
@RequiresPermission(conditional = true,
|
||
|
anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES})
|
||
|
public @NonNull List<SessionInfo> 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.
|
||
|
*
|
||
|
* <h3>Request of the {@link #REQUEST_TYPE_SHORTCUT} type.</h3>
|
||
|
*
|
||
|
* <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.</h3>
|
||
|
*
|
||
|
* <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);
|
||
|
}
|
||
|
}
|
||
|
}
|