/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.DisplayMetrics; import android.util.Log; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Class for retrieving a list of launchable activities for the current user and any associated * managed profiles. This is mainly for use by launchers. Apps can be queried for each user profile. * Since the PackageManager will not deliver package broadcasts for other profiles, you can register * for package changes here. *
* To watch for managed profiles being added or removed, register for the following broadcasts: * {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}. *
* You can retrieve the list of profiles associated with this user with
* {@link UserManager#getUserProfiles()}.
*/
public class LauncherApps {
static final String TAG = "LauncherApps";
static final boolean DEBUG = false;
private Context mContext;
private ILauncherApps mService;
private PackageManager mPm;
private List 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 Typically, launcher applications cache all or most shortcut information
* in memory in order to show shortcuts without a delay.
*
* When a given launcher application wants to update its cache, such as when its process
* restarts, it can fetch shortcut information with this flag.
* The application can then check {@link ShortcutInfo#getLastChangedTimestamp()} for each
* shortcut, fetching a shortcut's non-key information only if that shortcut has been
* updated.
*
* @see ShortcutManager
*/
public static final int FLAG_GET_KEY_FIELDS_ONLY = 1 << 2;
/** @hide */
@IntDef(flag = true,
value = {
FLAG_MATCH_DYNAMIC,
FLAG_MATCH_PINNED,
FLAG_MATCH_MANIFEST,
FLAG_GET_KEY_FIELDS_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface QueryFlags {}
long mChangedSince;
@Nullable
String mPackage;
@Nullable
List Only the default launcher can access the shortcut information.
*
* Note when this method returns {@code false}, it may be a temporary situation because
* the user is trying a new launcher application. The user may decide to change the default
* launcher back to the calling application again, so even if a launcher application loses
* this permission, it does not have to purge pinned shortcut information.
* If the calling launcher application contains pinned shortcuts, they will still work,
* even though the caller no longer has the shortcut host permission.
*
* @throws IllegalStateException when the user is locked.
*
* @see ShortcutManager
*/
public boolean hasShortcutHostPermission() {
try {
return mService.hasShortcutHostPermission(mContext.getPackageName());
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
* Returns {@link ShortcutInfo}s that match {@code query}.
*
* Callers must be allowed to access the shortcut information, as defined in {@link
* #hasShortcutHostPermission()}.
*
* @param query result includes shortcuts matching this query.
* @param user The UserHandle of the profile.
*
* @return the IDs of {@link ShortcutInfo}s that match the query.
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
*
* @see ShortcutManager
*/
@Nullable
public List This API is NOT cumulative; this will replace all pinned shortcuts for the package.
* However, different launchers may have different set of pinned shortcuts.
*
* The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
* @param packageName The target package name.
* @param shortcutIds The IDs of the shortcut to be pinned.
* @param user The UserHandle of the profile.
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
*
* @see ShortcutManager
*/
public void pinShortcuts(@NonNull String packageName, @NonNull List The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
* @param density The preferred density of the icon, zero for default density. Use
* density DPI values from {@link DisplayMetrics}.
*
* @return The drawable associated with the shortcut.
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
*
* @see ShortcutManager
* @see #getShortcutBadgedIconDrawable(ShortcutInfo, int)
* @see DisplayMetrics
*/
public Drawable getShortcutIconDrawable(@NonNull ShortcutInfo shortcut, int density) {
if (shortcut.hasIconFile()) {
final ParcelFileDescriptor pfd = getShortcutIconFd(shortcut);
if (pfd == null) {
return null;
}
try {
final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
return (bmp == null) ? null : new BitmapDrawable(mContext.getResources(), bmp);
} finally {
try {
pfd.close();
} catch (IOException ignore) {
}
}
} else if (shortcut.hasIconResource()) {
try {
final int resId = shortcut.getIconResourceId();
if (resId == 0) {
return null; // Shouldn't happen but just in case.
}
final ApplicationInfo ai = getApplicationInfo(shortcut.getPackage(),
/* flags =*/ 0, shortcut.getUserHandle());
final Resources res = mContext.getPackageManager().getResourcesForApplication(ai);
return res.getDrawableForDensity(resId, density);
} catch (NameNotFoundException | Resources.NotFoundException e) {
return null;
}
} else {
return null; // Has no icon.
}
}
/**
* Returns the shortcut icon with badging appropriate for the profile.
*
* The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
* @param density Optional density for the icon, or 0 to use the default density. Use
* @return A badged icon for the shortcut.
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
*
* @see ShortcutManager
* @see #getShortcutIconDrawable(ShortcutInfo, int)
* @see DisplayMetrics
*/
public Drawable getShortcutBadgedIconDrawable(ShortcutInfo shortcut, int density) {
final Drawable originalIcon = getShortcutIconDrawable(shortcut, density);
return (originalIcon == null) ? null : mContext.getPackageManager().getUserBadgedIcon(
originalIcon, shortcut.getUserHandle());
}
/**
* Starts a shortcut.
*
* The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
* @param packageName The target shortcut package name.
* @param shortcutId The target shortcut ID.
* @param sourceBounds The Rect containing the source bounds of the clicked icon.
* @param startActivityOptions Options to pass to startActivity.
* @param user The UserHandle of the profile.
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
*
* @throws android.content.ActivityNotFoundException failed to start shortcut. (e.g.
* the shortcut no longer exists, is disabled, the intent receiver activity doesn't exist, etc)
*/
public void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
@NonNull UserHandle user) {
startShortcut(packageName, shortcutId, sourceBounds, startActivityOptions,
user.getIdentifier());
}
/**
* Launches a shortcut.
*
* The calling launcher application must be allowed to access the shortcut information,
* as defined in {@link #hasShortcutHostPermission()}.
*
* @param shortcut The target shortcut.
* @param sourceBounds The Rect containing the source bounds of the clicked icon.
* @param startActivityOptions Options to pass to startActivity.
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
*
* @throws android.content.ActivityNotFoundException failed to start shortcut. (e.g.
* the shortcut no longer exists, is disabled, the intent receiver activity doesn't exist, etc)
*/
public void startShortcut(@NonNull ShortcutInfo shortcut,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
startShortcut(shortcut.getPackage(), shortcut.getId(),
sourceBounds, startActivityOptions,
shortcut.getUserId());
}
private void startShortcut(@NonNull String packageName, @NonNull String shortcutId,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions,
int userId) {
try {
final boolean success =
mService.startShortcut(mContext.getPackageName(), packageName, shortcutId,
sourceBounds, startActivityOptions, userId);
if (!success) {
throw new ActivityNotFoundException("Shortcut could not be started");
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Registers a callback for changes to packages in current and managed profiles.
*
* @param callback The callback to register.
*/
public void registerCallback(Callback callback) {
registerCallback(callback, null);
}
/**
* Registers a callback for changes to packages in current and managed profiles.
*
* @param callback The callback to register.
* @param handler that should be used to post callbacks on, may be null.
*/
public void registerCallback(Callback callback, Handler handler) {
synchronized (this) {
if (callback != null && findCallbackLocked(callback) < 0) {
boolean addedFirstCallback = mCallbacks.size() == 0;
addCallbackLocked(callback, handler);
if (addedFirstCallback) {
try {
mService.addOnAppsChangedListener(mContext.getPackageName(),
mAppsChangedListener);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
}
}
}
/**
* Unregisters a callback that was previously registered.
*
* @param callback The callback to unregister.
* @see #registerCallback(Callback)
*/
public void unregisterCallback(Callback callback) {
synchronized (this) {
removeCallbackLocked(callback);
if (mCallbacks.size() == 0) {
try {
mService.removeOnAppsChangedListener(mAppsChangedListener);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
}
}
/** @return position in mCallbacks for callback or -1 if not present. */
private int findCallbackLocked(Callback callback) {
if (callback == null) {
throw new IllegalArgumentException("Callback cannot be null");
}
final int size = mCallbacks.size();
for (int i = 0; i < size; ++i) {
if (mCallbacks.get(i).mCallback == callback) {
return i;
}
}
return -1;
}
private void removeCallbackLocked(Callback callback) {
int pos = findCallbackLocked(callback);
if (pos >= 0) {
mCallbacks.remove(pos);
}
}
private void addCallbackLocked(Callback callback, Handler handler) {
// Remove if already present.
removeCallbackLocked(callback);
if (handler == null) {
handler = new Handler();
}
CallbackMessageHandler toAdd = new CallbackMessageHandler(handler.getLooper(), callback);
mCallbacks.add(toAdd);
}
private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {
@Override
public void onPackageRemoved(UserHandle user, String packageName)
throws RemoteException {
if (DEBUG) {
Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName);
}
synchronized (LauncherApps.this) {
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnPackageRemoved(packageName, user);
}
}
}
@Override
public void onPackageChanged(UserHandle user, String packageName) throws RemoteException {
if (DEBUG) {
Log.d(TAG, "onPackageChanged " + user.getIdentifier() + "," + packageName);
}
synchronized (LauncherApps.this) {
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnPackageChanged(packageName, user);
}
}
}
@Override
public void onPackageAdded(UserHandle user, String packageName) throws RemoteException {
if (DEBUG) {
Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName);
}
synchronized (LauncherApps.this) {
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnPackageAdded(packageName, user);
}
}
}
@Override
public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing)
throws RemoteException {
if (DEBUG) {
Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames);
}
synchronized (LauncherApps.this) {
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnPackagesAvailable(packageNames, user, replacing);
}
}
}
@Override
public void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing)
throws RemoteException {
if (DEBUG) {
Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames);
}
synchronized (LauncherApps.this) {
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnPackagesUnavailable(packageNames, user, replacing);
}
}
}
@Override
public void onPackagesSuspended(UserHandle user, String[] packageNames)
throws RemoteException {
if (DEBUG) {
Log.d(TAG, "onPackagesSuspended " + user.getIdentifier() + "," + packageNames);
}
synchronized (LauncherApps.this) {
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnPackagesSuspended(packageNames, user);
}
}
}
@Override
public void onPackagesUnsuspended(UserHandle user, String[] packageNames)
throws RemoteException {
if (DEBUG) {
Log.d(TAG, "onPackagesUnsuspended " + user.getIdentifier() + "," + packageNames);
}
synchronized (LauncherApps.this) {
for (CallbackMessageHandler callback : mCallbacks) {
callback.postOnPackagesUnsuspended(packageNames, user);
}
}
}
@Override
public void onShortcutChanged(UserHandle user, String packageName,
ParceledListSlice shortcuts) {
if (DEBUG) {
Log.d(TAG, "onShortcutChanged " + user.getIdentifier() + "," + packageName);
}
final List
*
*/
public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
mQueryFlags = queryFlags;
return this;
}
}
/** @hide */
public LauncherApps(Context context, ILauncherApps service) {
mContext = context;
mService = service;
mPm = context.getPackageManager();
}
/** @hide */
@TestApi
public LauncherApps(Context context) {
this(context, ILauncherApps.Stub.asInterface(
ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE)));
}
/**
* Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and
* {@link Intent#CATEGORY_LAUNCHER}, for a specified user.
*
* @param packageName The specific package to query. If null, it checks all installed packages
* in the profile.
* @param user The UserHandle of the profile.
* @return List of launchable activities. Can be an empty list but will not be null.
*/
public List