blob: 24b9f48e71a646aa57e02cecb06c38471501c283 [file] [log] [blame]
/*
* 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 com.android.server.pm;
import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.LocusId;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageManager;
import android.content.pm.IShortcutChangeCallback;
import android.content.pm.IncrementalStatesInfo;
import android.content.pm.LauncherActivityInfoInternal;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutQueryWrapper;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IInterface;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
/**
* Service that manages requests and callbacks for launchers that support
* managed profiles.
*/
public class LauncherAppsService extends SystemService {
private final LauncherAppsImpl mLauncherAppsImpl;
public LauncherAppsService(Context context) {
super(context);
mLauncherAppsImpl = new LauncherAppsImpl(context);
}
@Override
public void onStart() {
publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl);
mLauncherAppsImpl.registerLoadingProgressForIncrementalApps();
}
static class BroadcastCookie {
public final UserHandle user;
public final String packageName;
public final int callingUid;
public final int callingPid;
BroadcastCookie(UserHandle userHandle, String packageName, int callingPid, int callingUid) {
this.user = userHandle;
this.packageName = packageName;
this.callingUid = callingUid;
this.callingPid = callingPid;
}
}
@VisibleForTesting
static class LauncherAppsImpl extends ILauncherApps.Stub {
private static final boolean DEBUG = false;
private static final String TAG = "LauncherAppsService";
private final Context mContext;
private final UserManager mUm;
private final IPackageManager mIPM;
private final UserManagerInternal mUserManagerInternal;
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final ShortcutServiceInternal mShortcutServiceInternal;
private final PackageManagerInternal mPackageManagerInternal;
private final PackageCallbackList<IOnAppsChangedListener> mListeners
= new PackageCallbackList<IOnAppsChangedListener>();
private final DevicePolicyManager mDpm;
private final PackageRemovedListener mPackageRemovedListener =
new PackageRemovedListener();
private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
@GuardedBy("mListeners")
private boolean mIsWatchingPackageBroadcasts = false;
private final ShortcutChangeHandler mShortcutChangeHandler;
private final Handler mCallbackHandler;
private PackageInstallerService mPackageInstallerService;
public LauncherAppsImpl(Context context) {
mContext = context;
mIPM = AppGlobals.getPackageManager();
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mUserManagerInternal = Objects.requireNonNull(
LocalServices.getService(UserManagerInternal.class));
mUsageStatsManagerInternal = Objects.requireNonNull(
LocalServices.getService(UsageStatsManagerInternal.class));
mActivityManagerInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
mActivityTaskManagerInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
mShortcutServiceInternal = Objects.requireNonNull(
LocalServices.getService(ShortcutServiceInternal.class));
mPackageManagerInternal = Objects.requireNonNull(
LocalServices.getService(PackageManagerInternal.class));
mShortcutServiceInternal.addListener(mPackageMonitor);
mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal);
mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
mCallbackHandler = BackgroundThread.getHandler();
mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
}
@VisibleForTesting
int injectBinderCallingUid() {
return getCallingUid();
}
@VisibleForTesting
int injectBinderCallingPid() {
return getCallingPid();
}
final int injectCallingUserId() {
return UserHandle.getUserId(injectBinderCallingUid());
}
@VisibleForTesting
long injectClearCallingIdentity() {
return Binder.clearCallingIdentity();
}
// Injection point.
@VisibleForTesting
void injectRestoreCallingIdentity(long token) {
Binder.restoreCallingIdentity(token);
}
private int getCallingUserId() {
return UserHandle.getUserId(injectBinderCallingUid());
}
/*
* @see android.content.pm.ILauncherApps#addOnAppsChangedListener
*/
@Override
public void addOnAppsChangedListener(String callingPackage, IOnAppsChangedListener listener)
throws RemoteException {
verifyCallingPackage(callingPackage);
synchronized (mListeners) {
if (DEBUG) {
Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle());
}
if (mListeners.getRegisteredCallbackCount() == 0) {
if (DEBUG) {
Log.d(TAG, "Starting package monitoring");
}
startWatchingPackageBroadcasts();
}
mListeners.unregister(listener);
mListeners.register(listener, new BroadcastCookie(UserHandle.of(getCallingUserId()),
callingPackage, injectBinderCallingPid(), injectBinderCallingUid()));
}
}
/*
* @see android.content.pm.ILauncherApps#removeOnAppsChangedListener
*/
@Override
public void removeOnAppsChangedListener(IOnAppsChangedListener listener)
throws RemoteException {
synchronized (mListeners) {
if (DEBUG) {
Log.d(TAG, "Removing listener from " + Binder.getCallingUserHandle());
}
mListeners.unregister(listener);
if (mListeners.getRegisteredCallbackCount() == 0) {
stopWatchingPackageBroadcasts();
}
}
}
/**
* @see android.content.pm.ILauncherApps#registerPackageInstallerCallback
*/
@Override
public void registerPackageInstallerCallback(String callingPackage,
IPackageInstallerCallback callback) {
verifyCallingPackage(callingPackage);
UserHandle callingIdUserHandle = new UserHandle(getCallingUserId());
getPackageInstallerService().registerCallback(callback, eventUserId ->
isEnabledProfileOf(callingIdUserHandle,
new UserHandle(eventUserId), "shouldReceiveEvent"));
}
@Override
public ParceledListSlice<SessionInfo> getAllSessions(String callingPackage) {
verifyCallingPackage(callingPackage);
List<SessionInfo> sessionInfos = new ArrayList<>();
int[] userIds = mUm.getEnabledProfileIds(getCallingUserId());
final long token = Binder.clearCallingIdentity();
try {
for (int userId : userIds) {
sessionInfos.addAll(getPackageInstallerService().getAllSessions(userId)
.getList());
}
} finally {
Binder.restoreCallingIdentity(token);
}
return new ParceledListSlice<>(sessionInfos);
}
private PackageInstallerService getPackageInstallerService() {
if (mPackageInstallerService == null) {
mPackageInstallerService = ((PackageInstallerService) ((PackageManagerService)
ServiceManager.getService("package")).getPackageInstaller());
}
return mPackageInstallerService;
}
/**
* Register a receiver to watch for package broadcasts
*/
private void startWatchingPackageBroadcasts() {
if (!mIsWatchingPackageBroadcasts) {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED_INTERNAL);
filter.addDataScheme("package");
mContext.registerReceiverAsUser(mPackageRemovedListener, UserHandle.ALL, filter,
/* broadcastPermission= */ null, mCallbackHandler);
mPackageMonitor.register(mContext, UserHandle.ALL, true, mCallbackHandler);
mIsWatchingPackageBroadcasts = true;
}
}
/**
* Unregister package broadcast receiver
*/
private void stopWatchingPackageBroadcasts() {
if (DEBUG) {
Log.d(TAG, "Stopped watching for packages");
}
if (mIsWatchingPackageBroadcasts) {
mContext.unregisterReceiver(mPackageRemovedListener);
mPackageMonitor.unregister();
mIsWatchingPackageBroadcasts = false;
}
}
void checkCallbackCount() {
synchronized (mListeners) {
if (DEBUG) {
Log.d(TAG, "Callback count = " + mListeners.getRegisteredCallbackCount());
}
if (mListeners.getRegisteredCallbackCount() == 0) {
stopWatchingPackageBroadcasts();
}
}
}
/**
* Checks if the calling user is in the same group as {@code targetUser}, and allowed
* to access it.
*
* @return TRUE if the calling user can access {@code targetUserId}. FALSE if not *but
* they're still in the same profile group*.
*
* @throws SecurityException if the calling user and {@code targetUser} are not in the same
* group.
*/
private boolean canAccessProfile(int targetUserId, String message) {
final int callingUserId = injectCallingUserId();
if (targetUserId == callingUserId) return true;
if (injectHasInteractAcrossUsersFullPermission(injectBinderCallingPid(),
injectBinderCallingUid())) {
return true;
}
long ident = injectClearCallingIdentity();
try {
final UserInfo callingUserInfo = mUm.getUserInfo(callingUserId);
if (callingUserInfo != null && callingUserInfo.isProfile()) {
Slog.w(TAG, message + " for another profile "
+ targetUserId + " from " + callingUserId + " not allowed");
return false;
}
} finally {
injectRestoreCallingIdentity(ident);
}
return mUserManagerInternal.isProfileAccessible(injectCallingUserId(), targetUserId,
message, true);
}
@VisibleForTesting // We override it in unit tests
void verifyCallingPackage(String callingPackage) {
int packageUid = -1;
try {
packageUid = AppGlobals.getPackageManager().getPackageUid(callingPackage,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES,
UserHandle.getUserId(getCallingUid()));
} catch (RemoteException ignore) {
}
if (packageUid < 0) {
Log.e(TAG, "Package not found: " + callingPackage);
}
if (packageUid != injectBinderCallingUid()) {
throw new SecurityException("Calling package name mismatch");
}
}
private LauncherActivityInfoInternal getHiddenAppActivityInfo(String packageName,
int callingUid, UserHandle user) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(packageName,
PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME));
final List<LauncherActivityInfoInternal> apps = queryIntentLauncherActivities(intent,
callingUid, user);
if (apps.size() > 0) {
return apps.get(0);
}
return null;
}
@Override
public boolean shouldHideFromSuggestions(String packageName, UserHandle user) {
if (!canAccessProfile(user.getIdentifier(), "cannot get shouldHideFromSuggestions")) {
return false;
}
final int flags = mPackageManagerInternal.getDistractingPackageRestrictions(packageName,
user.getIdentifier());
return (flags & PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS) != 0;
}
@Override
public ParceledListSlice<LauncherActivityInfoInternal> getLauncherActivities(
String callingPackage, String packageName, UserHandle user) throws RemoteException {
ParceledListSlice<LauncherActivityInfoInternal> launcherActivities =
queryActivitiesForUser(callingPackage,
new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setPackage(packageName),
user);
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.SHOW_HIDDEN_LAUNCHER_ICON_APPS_ENABLED, 1) == 0) {
return launcherActivities;
}
if (launcherActivities == null) {
// Cannot access profile, so we don't even return any hidden apps.
return null;
}
final int callingUid = injectBinderCallingUid();
final long ident = injectClearCallingIdentity();
try {
if (mUm.getUserInfo(user.getIdentifier()).isManagedProfile()) {
// Managed profile should not show hidden apps
return launcherActivities;
}
if (mDpm.getDeviceOwnerComponentOnAnyUser() != null) {
// Device owner devices should not show hidden apps
return launcherActivities;
}
final ArrayList<LauncherActivityInfoInternal> result = new ArrayList<>(
launcherActivities.getList());
if (packageName != null) {
// If this hidden app should not be shown, return the original list.
// Otherwise, inject hidden activity that forwards user to app details page.
if (result.size() > 0) {
return launcherActivities;
}
final ApplicationInfo appInfo = mPackageManagerInternal.getApplicationInfo(
packageName, /* flags= */ 0, callingUid, user.getIdentifier());
if (shouldShowSyntheticActivity(user, appInfo)) {
LauncherActivityInfoInternal info = getHiddenAppActivityInfo(packageName,
callingUid, user);
if (info != null) {
result.add(info);
}
}
return new ParceledListSlice<>(result);
}
final HashSet<String> visiblePackages = new HashSet<>();
for (LauncherActivityInfoInternal info : result) {
visiblePackages.add(info.getActivityInfo().packageName);
}
final List<ApplicationInfo> installedPackages =
mPackageManagerInternal.getInstalledApplications(/* flags= */ 0,
user.getIdentifier(), callingUid);
for (ApplicationInfo applicationInfo : installedPackages) {
if (!visiblePackages.contains(applicationInfo.packageName)) {
if (!shouldShowSyntheticActivity(user, applicationInfo)) {
continue;
}
LauncherActivityInfoInternal info = getHiddenAppActivityInfo(
applicationInfo.packageName, callingUid, user);
if (info != null) {
result.add(info);
}
}
}
return new ParceledListSlice<>(result);
} finally {
injectRestoreCallingIdentity(ident);
}
}
private boolean shouldShowSyntheticActivity(UserHandle user, ApplicationInfo appInfo) {
if (appInfo == null || appInfo.isSystemApp() || appInfo.isUpdatedSystemApp()) {
return false;
}
if (isManagedProfileAdmin(user, appInfo.packageName)) {
return false;
}
final AndroidPackage pkg = mPackageManagerInternal.getPackage(appInfo.packageName);
if (pkg == null) {
// Should not happen, but we shouldn't be failing if it does
return false;
}
// If app does not have any default enabled launcher activity or any permissions,
// the app can legitimately have no icon so we do not show the synthetic activity.
return requestsPermissions(pkg) && hasDefaultEnableLauncherActivity(
appInfo.packageName);
}
private boolean requestsPermissions(@NonNull AndroidPackage pkg) {
return !ArrayUtils.isEmpty(pkg.getRequestedPermissions());
}
private boolean hasDefaultEnableLauncherActivity(@NonNull String packageName) {
final Intent matchIntent = new Intent(Intent.ACTION_MAIN);
matchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
matchIntent.setPackage(packageName);
final List<ResolveInfo> infoList = mPackageManagerInternal.queryIntentActivities(
matchIntent, matchIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.MATCH_DISABLED_COMPONENTS, Binder.getCallingUid(),
getCallingUserId());
final int size = infoList.size();
for (int i = 0; i < size; i++) {
if (infoList.get(i).activityInfo.enabled) {
return true;
}
}
return false;
}
private boolean isManagedProfileAdmin(UserHandle user, String packageName) {
final List<UserInfo> userInfoList = mUm.getProfiles(user.getIdentifier());
for (int i = 0; i < userInfoList.size(); i++) {
UserInfo userInfo = userInfoList.get(i);
if (!userInfo.isManagedProfile()) {
continue;
}
ComponentName componentName = mDpm.getProfileOwnerAsUser(userInfo.getUserHandle());
if (componentName == null) {
continue;
}
if (componentName.getPackageName().equals(packageName)) {
return true;
}
}
return false;
}
@Override
public LauncherActivityInfoInternal resolveLauncherActivityInternal(
String callingPackage, ComponentName component, UserHandle user)
throws RemoteException {
if (!canAccessProfile(user.getIdentifier(), "Cannot resolve activity")) {
return null;
}
final int callingUid = injectBinderCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
final ActivityInfo activityInfo = mPackageManagerInternal.getActivityInfo(component,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
callingUid, user.getIdentifier());
if (activityInfo == null) {
return null;
}
if (component == null || component.getPackageName() == null) {
// should not happen
return null;
}
final IncrementalStatesInfo incrementalStatesInfo =
mPackageManagerInternal.getIncrementalStatesInfo(component.getPackageName(),
callingUid, user.getIdentifier());
if (incrementalStatesInfo == null) {
// package does not exist; should not happen
return null;
}
return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public ParceledListSlice getShortcutConfigActivities(
String callingPackage, String packageName, UserHandle user)
throws RemoteException {
return queryActivitiesForUser(callingPackage,
new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName), user);
}
private ParceledListSlice<LauncherActivityInfoInternal> queryActivitiesForUser(
String callingPackage, Intent intent, UserHandle user) {
if (!canAccessProfile(user.getIdentifier(), "Cannot retrieve activities")) {
return null;
}
final int callingUid = injectBinderCallingUid();
long ident = injectClearCallingIdentity();
try {
return new ParceledListSlice<>(queryIntentLauncherActivities(intent, callingUid,
user));
} finally {
injectRestoreCallingIdentity(ident);
}
}
private List<LauncherActivityInfoInternal> queryIntentLauncherActivities(
Intent intent, int callingUid, UserHandle user) {
final List<ResolveInfo> apps = mPackageManagerInternal.queryIntentActivities(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
callingUid, user.getIdentifier());
final int numResolveInfos = apps.size();
List<LauncherActivityInfoInternal> results = new ArrayList<>();
for (int i = 0; i < numResolveInfos; i++) {
final ResolveInfo ri = apps.get(i);
final String packageName = ri.activityInfo.packageName;
if (packageName == null) {
// should not happen
continue;
}
final IncrementalStatesInfo incrementalStatesInfo =
mPackageManagerInternal.getIncrementalStatesInfo(packageName, callingUid,
user.getIdentifier());
if (incrementalStatesInfo == null) {
// package doesn't exist any more; should not happen
continue;
}
results.add(new LauncherActivityInfoInternal(ri.activityInfo,
incrementalStatesInfo));
}
return results;
}
@Override
public IntentSender getShortcutConfigActivityIntent(String callingPackage,
ComponentName component, UserHandle user) throws RemoteException {
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(user.getIdentifier(), "Cannot check package")) {
return null;
}
Objects.requireNonNull(component);
// All right, create the sender.
final int callingUid = injectBinderCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
Intent packageIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
.setPackage(component.getPackageName());
List<ResolveInfo> apps =
mPackageManagerInternal.queryIntentActivities(packageIntent,
packageIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
callingUid, user.getIdentifier());
// ensure that the component is present in the list
if (!apps.stream().anyMatch(
ri -> component.getClassName().equals(ri.activityInfo.name))) {
return null;
}
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(component);
final PendingIntent pi = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
null, user);
return pi == null ? null : pi.getIntentSender();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Returns the intents for a specific shortcut.
*/
@Nullable
@Override
public PendingIntent getShortcutIntent(@NonNull final String callingPackage,
@NonNull final String packageName, @NonNull final String shortcutId,
@Nullable final Bundle opts, @NonNull final UserHandle user)
throws RemoteException {
Objects.requireNonNull(callingPackage);
Objects.requireNonNull(packageName);
Objects.requireNonNull(shortcutId);
Objects.requireNonNull(user);
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(user.getIdentifier(), "Cannot get shortcuts")) {
return null;
}
final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
getCallingUserId(), callingPackage, packageName, shortcutId,
user.getIdentifier(), injectBinderCallingPid(), injectBinderCallingUid());
if (intents == null || intents.length == 0) {
return null;
}
final long ident = Binder.clearCallingIdentity();
try {
return injectCreatePendingIntent(0 /* requestCode */, intents,
FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT, opts, packageName,
mPackageManagerInternal.getPackageUid(
packageName, PackageManager.MATCH_DIRECT_BOOT_AUTO,
user.getIdentifier()));
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public boolean isPackageEnabled(String callingPackage, String packageName, UserHandle user)
throws RemoteException {
if (!canAccessProfile(user.getIdentifier(), "Cannot check package")) {
return false;
}
final int callingUid = injectBinderCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
final PackageInfo info = mPackageManagerInternal.getPackageInfo(packageName,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
callingUid, user.getIdentifier());
return info != null && info.applicationInfo.enabled;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public Bundle getSuspendedPackageLauncherExtras(String packageName,
UserHandle user) {
if (!canAccessProfile(user.getIdentifier(), "Cannot get launcher extras")) {
return null;
}
return mPackageManagerInternal.getSuspendedPackageLauncherExtras(packageName,
user.getIdentifier());
}
@Override
public ApplicationInfo getApplicationInfo(
String callingPackage, String packageName, int flags, UserHandle user)
throws RemoteException {
if (!canAccessProfile(user.getIdentifier(), "Cannot check package")) {
return null;
}
final int callingUid = injectBinderCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
final ApplicationInfo info = mPackageManagerInternal.getApplicationInfo(packageName,
flags, callingUid, user.getIdentifier());
return info;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public LauncherApps.AppUsageLimit getAppUsageLimit(String callingPackage,
String packageName, UserHandle user) {
verifyCallingPackage(callingPackage);
if (!canAccessProfile(user.getIdentifier(), "Cannot access usage limit")) {
return null;
}
if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
throw new SecurityException("Caller is not the recents app");
}
final UsageStatsManagerInternal.AppUsageLimitData data =
mUsageStatsManagerInternal.getAppUsageLimit(packageName, user);
if (data == null) {
return null;
}
return new LauncherApps.AppUsageLimit(
data.getTotalUsageLimit(), data.getUsageRemaining());
}
private void ensureShortcutPermission(@NonNull String callingPackage) {
verifyCallingPackage(callingPackage);
if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
callingPackage, injectBinderCallingPid(), injectBinderCallingUid())) {
throw new SecurityException("Caller can't access shortcut information");
}
}
private void ensureStrictAccessShortcutsPermission(@NonNull String callingPackage) {
verifyCallingPackage(callingPackage);
if (!injectHasAccessShortcutsPermission(injectBinderCallingPid(),
injectBinderCallingUid())) {
throw new SecurityException("Caller can't access shortcut information");
}
}
/**
* Returns true if the caller has the "ACCESS_SHORTCUTS" permission.
*/
@VisibleForTesting
boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS,
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
}
/**
* Returns true if the caller has the "INTERACT_ACROSS_USERS_FULL" permission.
*/
@VisibleForTesting
boolean injectHasInteractAcrossUsersFullPermission(int callingPid, int callingUid) {
return mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
}
@VisibleForTesting
PendingIntent injectCreatePendingIntent(int requestCode, @NonNull Intent[] intents,
int flags, Bundle options, String ownerPackage, int ownerUserId) {
return mActivityManagerInternal.getPendingIntentActivityAsApp(requestCode, intents,
flags, null /* options */, ownerPackage, ownerUserId);
}
@Override
public ParceledListSlice getShortcuts(@NonNull final String callingPackage,
@NonNull final ShortcutQueryWrapper query, @NonNull final UserHandle targetUser) {
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) {
return new ParceledListSlice<>(Collections.EMPTY_LIST);
}
final long changedSince = query.getChangedSince();
final String packageName = query.getPackage();
final List<String> shortcutIds = query.getShortcutIds();
final List<LocusId> locusIds = query.getLocusIds();
final ComponentName componentName = query.getActivity();
final int flags = query.getQueryFlags();
if (shortcutIds != null && packageName == null) {
throw new IllegalArgumentException(
"To query by shortcut ID, package name must also be set");
}
if (locusIds != null && packageName == null) {
throw new IllegalArgumentException(
"To query by locus ID, package name must also be set");
}
if ((query.getQueryFlags() & ShortcutQuery.FLAG_GET_PERSONS_DATA) != 0) {
ensureStrictAccessShortcutsPermission(callingPackage);
}
// TODO(b/29399275): Eclipse compiler requires explicit List<ShortcutInfo> cast below.
return new ParceledListSlice<>((List<ShortcutInfo>)
mShortcutServiceInternal.getShortcuts(getCallingUserId(),
callingPackage, changedSince, packageName, shortcutIds, locusIds,
componentName, flags, targetUser.getIdentifier(),
injectBinderCallingPid(), injectBinderCallingUid()));
}
@Override
public void registerShortcutChangeCallback(@NonNull final String callingPackage,
@NonNull final ShortcutQueryWrapper query,
@NonNull final IShortcutChangeCallback callback) {
ensureShortcutPermission(callingPackage);
if (query.getShortcutIds() != null && query.getPackage() == null) {
throw new IllegalArgumentException(
"To query by shortcut ID, package name must also be set");
}
if (query.getLocusIds() != null && query.getPackage() == null) {
throw new IllegalArgumentException(
"To query by locus ID, package name must also be set");
}
UserHandle user = UserHandle.of(injectCallingUserId());
if (injectHasInteractAcrossUsersFullPermission(injectBinderCallingPid(),
injectBinderCallingUid())) {
user = null;
}
mShortcutChangeHandler.addShortcutChangeCallback(callback, query, user);
}
@Override
public void unregisterShortcutChangeCallback(String callingPackage,
IShortcutChangeCallback callback) {
ensureShortcutPermission(callingPackage);
mShortcutChangeHandler.removeShortcutChangeCallback(callback);
}
@Override
public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle targetUser) {
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) {
return;
}
mShortcutServiceInternal.pinShortcuts(getCallingUserId(),
callingPackage, packageName, ids, targetUser.getIdentifier());
}
@Override
public void cacheShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle targetUser, int cacheFlags) {
ensureStrictAccessShortcutsPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot cache shortcuts")) {
return;
}
mShortcutServiceInternal.cacheShortcuts(
getCallingUserId(), callingPackage, packageName, ids,
targetUser.getIdentifier(), toShortcutsCacheFlags(cacheFlags));
}
@Override
public void uncacheShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle targetUser, int cacheFlags) {
ensureStrictAccessShortcutsPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot uncache shortcuts")) {
return;
}
mShortcutServiceInternal.uncacheShortcuts(
getCallingUserId(), callingPackage, packageName, ids,
targetUser.getIdentifier(), toShortcutsCacheFlags(cacheFlags));
}
@Override
public int getShortcutIconResId(String callingPackage, String packageName, String id,
int targetUserId) {
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(targetUserId, "Cannot access shortcuts")) {
return 0;
}
return mShortcutServiceInternal.getShortcutIconResId(getCallingUserId(),
callingPackage, packageName, id, targetUserId);
}
@Override
public ParcelFileDescriptor getShortcutIconFd(String callingPackage,
String packageName, String id, int targetUserId) {
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(targetUserId, "Cannot access shortcuts")) {
return null;
}
return mShortcutServiceInternal.getShortcutIconFd(getCallingUserId(),
callingPackage, packageName, id, targetUserId);
}
@Override
public String getShortcutIconUri(String callingPackage, String packageName,
String shortcutId, int userId) {
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(userId, "Cannot access shortcuts")) {
return null;
}
return mShortcutServiceInternal.getShortcutIconUri(getCallingUserId(), callingPackage,
packageName, shortcutId, userId);
}
@Override
public boolean hasShortcutHostPermission(String callingPackage) {
verifyCallingPackage(callingPackage);
return mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
callingPackage, injectBinderCallingPid(), injectBinderCallingUid());
}
@Override
public boolean startShortcut(String callingPackage, String packageName, String featureId,
String shortcutId, Rect sourceBounds, Bundle startActivityOptions,
int targetUserId) {
verifyCallingPackage(callingPackage);
if (!canAccessProfile(targetUserId, "Cannot start activity")) {
return false;
}
// Even without the permission, pinned shortcuts are always launchable.
if (!mShortcutServiceInternal.isPinnedByCaller(getCallingUserId(),
callingPackage, packageName, shortcutId, targetUserId)) {
ensureShortcutPermission(callingPackage);
}
final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId,
injectBinderCallingPid(), injectBinderCallingUid());
if (intents == null || intents.length == 0) {
return false;
}
// Note the target activity doesn't have to be exported.
// Flag for bubble
ActivityOptions options = ActivityOptions.fromBundle(startActivityOptions);
if (options != null && options.isApplyActivityFlagsForBubbles()) {
// Flag for bubble to make behaviour match documentLaunchMode=always.
intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
}
intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intents[0].setSourceBounds(sourceBounds);
// Replace theme for splash screen
final String splashScreenThemeResName =
mShortcutServiceInternal.getShortcutStartingThemeResName(getCallingUserId(),
callingPackage, packageName, shortcutId, targetUserId);
if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) {
if (startActivityOptions == null) {
startActivityOptions = new Bundle();
}
startActivityOptions.putString(KEY_SPLASH_SCREEN_THEME, splashScreenThemeResName);
}
return startShortcutIntentsAsPublisher(
intents, packageName, featureId, startActivityOptions, targetUserId);
}
private boolean startShortcutIntentsAsPublisher(@NonNull Intent[] intents,
@NonNull String publisherPackage, @Nullable String publishedFeatureId,
Bundle startActivityOptions, int userId) {
final int code;
try {
code = mActivityTaskManagerInternal.startActivitiesAsPackage(publisherPackage,
publishedFeatureId, userId, intents, startActivityOptions);
if (ActivityManager.isStartResultSuccessful(code)) {
return true; // Success
} else {
Log.e(TAG, "Couldn't start activity, code=" + code);
}
return false;
} catch (SecurityException e) {
if (DEBUG) {
Slog.d(TAG, "SecurityException while launching intent", e);
}
return false;
}
}
@Override
public boolean isActivityEnabled(
String callingPackage, ComponentName component, UserHandle user)
throws RemoteException {
if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) {
return false;
}
final int callingUid = injectBinderCallingUid();
final int state = mPackageManagerInternal.getComponentEnabledSetting(component,
callingUid, user.getIdentifier());
switch (state) {
case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
break; // Need to check the manifest's enabled state.
case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
return true;
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
return false;
}
final long ident = Binder.clearCallingIdentity();
try {
final ActivityInfo info = mPackageManagerInternal.getActivityInfo(component,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
callingUid, user.getIdentifier());
// Note we don't check "exported" because if the caller has the same UID as the
// callee's UID, it can still be launched.
// (If an app doesn't export a front door activity and causes issues with the
// launcher, that's just the app's bug.)
return info != null && info.isEnabled();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void startSessionDetailsActivityAsUser(IApplicationThread caller,
String callingPackage, String callingFeatureId, SessionInfo sessionInfo,
Rect sourceBounds, Bundle opts, UserHandle userHandle) throws RemoteException {
int userId = userHandle.getIdentifier();
if (!canAccessProfile(userId, "Cannot start details activity")) {
return;
}
Intent i = new Intent(Intent.ACTION_VIEW)
.setData(new Uri.Builder()
.scheme("market")
.authority("details")
.appendQueryParameter("id", sessionInfo.appPackageName)
.build())
.putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app")
.authority(callingPackage).build());
i.setSourceBounds(sourceBounds);
mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage,
callingFeatureId, i, /* resultTo= */ null, Intent.FLAG_ACTIVITY_NEW_TASK, opts,
userId);
}
@Override
public PendingIntent getActivityLaunchIntent(ComponentName component, Bundle opts,
UserHandle user) {
if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
throw new ActivityNotFoundException("Activity could not be found");
}
final Intent launchIntent = getMainActivityLaunchIntent(component, user);
if (launchIntent == null) {
throw new SecurityException("Attempt to launch activity without "
+ " category Intent.CATEGORY_LAUNCHER " + component);
}
final long ident = Binder.clearCallingIdentity();
try {
// If we reach here, we've verified that the caller has access to the profile and
// is launching an exported activity with CATEGORY_LAUNCHER so we can clear the
// calling identity to mirror the startActivityAsUser() call which does not validate
// the calling user
return PendingIntent.getActivityAsUser(mContext, 0 /* requestCode */, launchIntent,
FLAG_IMMUTABLE, null /* options */, user);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
if (!canAccessProfile(user.getIdentifier(), "Cannot start activity")) {
return;
}
Intent launchIntent = getMainActivityLaunchIntent(component, user);
if (launchIntent == null) {
throw new SecurityException("Attempt to launch activity without "
+ " category Intent.CATEGORY_LAUNCHER " + component);
}
launchIntent.setSourceBounds(sourceBounds);
mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage,
callingFeatureId, launchIntent, /* resultTo= */ null,
Intent.FLAG_ACTIVITY_NEW_TASK, opts, user.getIdentifier());
}
/**
* Returns the main activity launch intent for the given component package.
*/
private Intent getMainActivityLaunchIntent(ComponentName component, UserHandle user) {
Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
launchIntent.setPackage(component.getPackageName());
boolean canLaunch = false;
final int callingUid = injectBinderCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
// Check that the component actually has Intent.CATEGORY_LAUCNCHER
// as calling startActivityAsUser ignores the category and just
// resolves based on the component if present.
final List<ResolveInfo> apps = mPackageManagerInternal.queryIntentActivities(
launchIntent,
launchIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
callingUid, user.getIdentifier());
final int size = apps.size();
for (int i = 0; i < size; ++i) {
ActivityInfo activityInfo = apps.get(i).activityInfo;
if (activityInfo.packageName.equals(component.getPackageName()) &&
activityInfo.name.equals(component.getClassName())) {
if (!activityInfo.exported) {
throw new SecurityException("Cannot launch non-exported components "
+ component);
}
// Found an activity with category launcher that matches
// this component so ok to launch.
launchIntent.setPackage(null);
launchIntent.setComponent(component);
canLaunch = true;
break;
}
}
if (!canLaunch) {
return null;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
return launchIntent;
}
@Override
public void showAppDetailsAsUser(IApplicationThread caller,
String callingPackage, String callingFeatureId, ComponentName component,
Rect sourceBounds, Bundle opts, UserHandle user) throws RemoteException {
if (!canAccessProfile(user.getIdentifier(), "Cannot show app details")) {
return;
}
final Intent intent;
final long ident = Binder.clearCallingIdentity();
try {
String packageName = component.getPackageName();
int uId = -1;
try {
uId = mContext.getPackageManager().getApplicationInfo(
packageName, PackageManager.MATCH_ANY_USER).uid;
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "package not found: " + e);
}
intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null));
intent.putExtra("uId", uId);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.setSourceBounds(sourceBounds);
} finally {
Binder.restoreCallingIdentity(ident);
}
mActivityTaskManagerInternal.startActivityAsUser(caller, callingPackage,
callingFeatureId, intent, /* resultTo= */ null, Intent.FLAG_ACTIVITY_NEW_TASK,
opts, user.getIdentifier());
}
/** Checks if user is a profile of or same as listeningUser.
* and the user is enabled. */
private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,
String debugMsg) {
return mUserManagerInternal.isProfileAccessible(listeningUser.getIdentifier(),
user.getIdentifier(), debugMsg, false);
}
/** Returns whether or not the result to the listener should be filtered. */
private boolean isPackageVisibleToListener(String packageName, BroadcastCookie cookie) {
return !mPackageManagerInternal.filterAppAccess(packageName, cookie.callingUid,
cookie.user.getIdentifier());
}
/** Returns whether or not the given appId is in allow list */
private static boolean isCallingAppIdAllowed(int[] appIdAllowList, @AppIdInt int appId) {
if (appIdAllowList == null) {
return true;
}
return Arrays.binarySearch(appIdAllowList, appId) > -1;
}
private String[] getFilteredPackageNames(String[] packageNames, BroadcastCookie cookie) {
final List<String> filteredPackageNames = new ArrayList<>();
for (String packageName : packageNames) {
if (!isPackageVisibleToListener(packageName, cookie)) {
continue;
}
filteredPackageNames.add(packageName);
}
return filteredPackageNames.toArray(new String[filteredPackageNames.size()]);
}
private int toShortcutsCacheFlags(int cacheFlags) {
int ret = 0;
if (cacheFlags == FLAG_CACHE_NOTIFICATION_SHORTCUTS) {
ret = ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
} else if (cacheFlags == FLAG_CACHE_BUBBLE_SHORTCUTS) {
ret = ShortcutInfo.FLAG_CACHED_BUBBLES;
} else if (cacheFlags == FLAG_CACHE_PEOPLE_TILE_SHORTCUTS) {
ret = ShortcutInfo.FLAG_CACHED_PEOPLE_TILE;
}
Preconditions.checkArgumentPositive(ret, "Invalid cache owner");
return ret;
}
@VisibleForTesting
void postToPackageMonitorHandler(Runnable r) {
mCallbackHandler.post(r);
}
/**
* Check all installed apps and if a package is installed via Incremental and not fully
* loaded, register loading progress listener.
*/
void registerLoadingProgressForIncrementalApps() {
final List<UserHandle> users = mUm.getUserProfiles();
if (users == null) {
return;
}
for (UserHandle user : users) {
mPackageManagerInternal.forEachInstalledPackage(pkg -> {
final String packageName = pkg.getPackageName();
if (mPackageManagerInternal.getIncrementalStatesInfo(packageName,
Process.myUid(), user.getIdentifier()).isLoading()) {
mPackageManagerInternal.registerInstalledLoadingProgressCallback(
packageName, new PackageLoadingProgressCallback(packageName, user),
user.getIdentifier());
}
}, user.getIdentifier());
}
}
public static class ShortcutChangeHandler implements LauncherApps.ShortcutChangeCallback {
private final UserManagerInternal mUserManagerInternal;
ShortcutChangeHandler(UserManagerInternal userManager) {
mUserManagerInternal = userManager;
}
private final RemoteCallbackList<IShortcutChangeCallback> mCallbacks =
new RemoteCallbackList<>();
public synchronized void addShortcutChangeCallback(IShortcutChangeCallback callback,
ShortcutQueryWrapper query, UserHandle user) {
mCallbacks.unregister(callback);
mCallbacks.register(callback, new Pair<>(query, user));
}
public synchronized void removeShortcutChangeCallback(
IShortcutChangeCallback callback) {
mCallbacks.unregister(callback);
}
@Override
public void onShortcutsAddedOrUpdated(String packageName, List<ShortcutInfo> shortcuts,
UserHandle user) {
onShortcutEvent(packageName, shortcuts, user, false);
}
@Override
public void onShortcutsRemoved(String packageName, List<ShortcutInfo> shortcuts,
UserHandle user) {
onShortcutEvent(packageName, shortcuts, user, true);
}
private void onShortcutEvent(String packageName,
List<ShortcutInfo> shortcuts, UserHandle user, boolean shortcutsRemoved) {
int count = mCallbacks.beginBroadcast();
for (int i = 0; i < count; i++) {
final IShortcutChangeCallback callback = mCallbacks.getBroadcastItem(i);
final Pair<ShortcutQueryWrapper, UserHandle> cookie =
(Pair<ShortcutQueryWrapper, UserHandle>)
mCallbacks.getBroadcastCookie(i);
final UserHandle callbackUser = cookie.second;
if (callbackUser != null && !hasUserAccess(callbackUser, user)) {
// Callback owner does not have access to the shortcuts' user.
continue;
}
// Filter the list by query, if any matches exists, send via callback.
List<ShortcutInfo> matchedList = filterShortcutsByQuery(packageName, shortcuts,
cookie.first, shortcutsRemoved);
if (!CollectionUtils.isEmpty(matchedList)) {
try {
if (shortcutsRemoved) {
callback.onShortcutsRemoved(packageName, matchedList, user);
} else {
callback.onShortcutsAddedOrUpdated(packageName, matchedList, user);
}
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing the dead object.
}
}
}
mCallbacks.finishBroadcast();
}
public static List<ShortcutInfo> filterShortcutsByQuery(String packageName,
List<ShortcutInfo> shortcuts, ShortcutQueryWrapper query,
boolean shortcutsRemoved) {
final long changedSince = query.getChangedSince();
final String queryPackage = query.getPackage();
final List<String> shortcutIds = query.getShortcutIds();
final List<LocusId> locusIds = query.getLocusIds();
final ComponentName activity = query.getActivity();
final int flags = query.getQueryFlags();
if (queryPackage != null && !queryPackage.equals(packageName)) {
return null;
}
List<ShortcutInfo> matches = new ArrayList<>();
final boolean matchDynamic = (flags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
final boolean matchPinned = (flags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
final boolean matchManifest = (flags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
final boolean matchCached = (flags & ShortcutQuery.FLAG_MATCH_CACHED) != 0;
final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0)
| (matchPinned ? ShortcutInfo.FLAG_PINNED : 0)
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
| (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
for (int i = 0; i < shortcuts.size(); i++) {
final ShortcutInfo si = shortcuts.get(i);
if (activity != null && !activity.equals(si.getActivity())) {
continue;
}
if (changedSince != 0 && changedSince > si.getLastChangedTimestamp()) {
continue;
}
if (shortcutIds != null && !shortcutIds.contains(si.getId())) {
continue;
}
if (locusIds != null && !locusIds.contains(si.getLocusId())) {
continue;
}
if (shortcutsRemoved || (shortcutFlags & si.getFlags()) != 0) {
matches.add(si);
}
}
return matches;
}
private boolean hasUserAccess(UserHandle callbackUser, UserHandle shortcutUser) {
final int callbackUserId = callbackUser.getIdentifier();
final int shortcutUserId = shortcutUser.getIdentifier();
if (shortcutUser == callbackUser) return true;
return mUserManagerInternal.isProfileAccessible(callbackUserId, shortcutUserId,
null, false);
}
}
private class PackageRemovedListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_NULL);
if (userId == UserHandle.USER_NULL) {
Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent);
return;
}
final String action = intent.getAction();
// Handle onPackageRemoved.
if (Intent.ACTION_PACKAGE_REMOVED_INTERNAL.equals(action)) {
final String packageName = getPackageName(intent);
final int[] appIdAllowList =
intent.getIntArrayExtra(Intent.EXTRA_VISIBILITY_ALLOW_LIST);
// If {@link #EXTRA_REPLACING} is true, that will be onPackageChanged case.
if (packageName != null && !intent.getBooleanExtra(
Intent.EXTRA_REPLACING, /* defaultValue= */ false)) {
final UserHandle user = new UserHandle(userId);
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
final IOnAppsChangedListener listener =
mListeners.getBroadcastItem(i);
final BroadcastCookie cookie =
(BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, user, "onPackageRemoved")) {
continue;
}
if (!isCallingAppIdAllowed(appIdAllowList, UserHandle.getAppId(
cookie.callingUid))) {
continue;
}
try {
listener.onPackageRemoved(user, packageName);
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} finally {
mListeners.finishBroadcast();
}
}
}
}
private String getPackageName(Intent intent) {
final Uri uri = intent.getData();
final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
return pkg;
}
}
private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener {
// TODO Simplify with lambdas.
@Override
public void onPackageAdded(String packageName, int uid) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, user, "onPackageAdded")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie)) {
continue;
}
try {
listener.onPackageAdded(user, packageName);
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} finally {
mListeners.finishBroadcast();
}
super.onPackageAdded(packageName, uid);
mPackageManagerInternal.registerInstalledLoadingProgressCallback(packageName,
new PackageLoadingProgressCallback(packageName, user),
user.getIdentifier());
}
@Override
public void onPackageModified(String packageName) {
onPackageChanged(packageName);
super.onPackageModified(packageName);
}
private void onPackageChanged(String packageName) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, user, "onPackageModified")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie)) {
continue;
}
try {
listener.onPackageChanged(user, packageName);
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} finally {
mListeners.finishBroadcast();
}
}
@Override
public void onPackagesAvailable(String[] packages) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, user, "onPackagesAvailable")) {
continue;
}
final String[] filteredPackages = getFilteredPackageNames(packages, cookie);
// If all packages are filtered, skip notifying listener.
if (ArrayUtils.isEmpty(filteredPackages)) {
continue;
}
try {
listener.onPackagesAvailable(user, filteredPackages, isReplacing());
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} finally {
mListeners.finishBroadcast();
}
super.onPackagesAvailable(packages);
}
@Override
public void onPackagesUnavailable(String[] packages) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, user, "onPackagesUnavailable")) {
continue;
}
final String[] filteredPackages = getFilteredPackageNames(packages, cookie);
// If all packages are filtered, skip notifying listener.
if (ArrayUtils.isEmpty(filteredPackages)) {
continue;
}
try {
listener.onPackagesUnavailable(user, filteredPackages, isReplacing());
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} finally {
mListeners.finishBroadcast();
}
super.onPackagesUnavailable(packages);
}
@Override
public void onPackagesSuspended(String[] packages) {
UserHandle user = new UserHandle(getChangingUserId());
final ArrayList<Pair<String, Bundle>> packagesWithExtras = new ArrayList<>();
final ArrayList<String> packagesWithoutExtras = new ArrayList<>();
for (String pkg : packages) {
final Bundle launcherExtras =
mPackageManagerInternal.getSuspendedPackageLauncherExtras(pkg,
user.getIdentifier());
if (launcherExtras != null) {
packagesWithExtras.add(new Pair<>(pkg, launcherExtras));
} else {
packagesWithoutExtras.add(pkg);
}
}
final String[] packagesNullExtras = packagesWithoutExtras.toArray(
new String[packagesWithoutExtras.size()]);
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, user, "onPackagesSuspended")) {
continue;
}
final String[] filteredPackagesWithoutExtras =
getFilteredPackageNames(packages, cookie);
// If all packages are filtered, skip notifying listener.
if (ArrayUtils.isEmpty(filteredPackagesWithoutExtras)) {
continue;
}
try {
listener.onPackagesSuspended(user, filteredPackagesWithoutExtras,
/* launcherExtras= */ null);
for (int idx = 0; idx < packagesWithExtras.size(); idx++) {
Pair<String, Bundle> packageExtraPair = packagesWithExtras.get(idx);
if (!isPackageVisibleToListener(packageExtraPair.first, cookie)) {
continue;
}
listener.onPackagesSuspended(user,
new String[]{packageExtraPair.first},
packageExtraPair.second);
}
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} finally {
mListeners.finishBroadcast();
}
}
@Override
public void onPackagesUnsuspended(String[] packages) {
UserHandle user = new UserHandle(getChangingUserId());
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, user, "onPackagesUnsuspended")) {
continue;
}
final String[] filteredPackages = getFilteredPackageNames(packages, cookie);
// If all packages are filtered, skip notifying listener.
if (ArrayUtils.isEmpty(filteredPackages)) {
continue;
}
try {
listener.onPackagesUnsuspended(user, filteredPackages);
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} finally {
mListeners.finishBroadcast();
}
super.onPackagesUnsuspended(packages);
}
@Override
public void onShortcutChanged(@NonNull String packageName,
@UserIdInt int userId) {
postToPackageMonitorHandler(() -> onShortcutChangedInner(packageName, userId));
}
private void onShortcutChangedInner(@NonNull String packageName,
@UserIdInt int userId) {
final int n = mListeners.beginBroadcast();
try {
final UserHandle user = UserHandle.of(userId);
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, user, "onShortcutChanged")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie)) {
continue;
}
final int launcherUserId = cookie.user.getIdentifier();
// Make sure the caller has the permission.
if (!mShortcutServiceInternal.hasShortcutHostPermission(
launcherUserId, cookie.packageName,
cookie.callingPid, cookie.callingUid)) {
continue;
}
// Each launcher has a different set of pinned shortcuts, so we need to do a
// query in here.
// (As of now, only one launcher has the permission at a time, so it's bit
// moot, but we may change the permission model eventually.)
final List<ShortcutInfo> list =
mShortcutServiceInternal.getShortcuts(launcherUserId,
cookie.packageName,
/* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
/* locusIds=*/ null, /* component= */ null,
ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
| ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED
, userId, cookie.callingPid, cookie.callingUid);
try {
listener.onShortcutChanged(user, packageName,
new ParceledListSlice<>(list));
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} catch (RuntimeException e) {
// When the user is locked we get IllegalState, so just catch all.
Log.w(TAG, e.getMessage(), e);
} finally {
mListeners.finishBroadcast();
}
}
@Override
public void onPackageStateChanged(String packageName, int uid) {
onPackageChanged(packageName);
super.onPackageStateChanged(packageName, uid);
}
}
class PackageCallbackList<T extends IInterface> extends RemoteCallbackList<T> {
@Override
public void onCallbackDied(T callback, Object cookie) {
checkCallbackCount();
}
}
class PackageLoadingProgressCallback extends
PackageManagerInternal.InstalledLoadingProgressCallback {
private String mPackageName;
private UserHandle mUser;
PackageLoadingProgressCallback(String packageName, UserHandle user) {
super(mCallbackHandler);
mPackageName = packageName;
mUser = user;
}
@Override
public void onLoadingProgressChanged(float progress) {
final int n = mListeners.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
if (!isEnabledProfileOf(cookie.user, mUser, "onLoadingProgressChanged")) {
continue;
}
if (!isPackageVisibleToListener(mPackageName, cookie)) {
continue;
}
try {
listener.onPackageLoadingProgressChanged(mUser, mPackageName, progress);
} catch (RemoteException re) {
Slog.d(TAG, "Callback failed ", re);
}
}
} finally {
mListeners.finishBroadcast();
}
}
}
}
}