blob: 02052808dd181b6fc009d09efdafbc77dc18b037 [file] [log] [blame]
/*
* Copyright (C) 2021 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.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.TAG;
import android.Manifest;
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
* Helper class to send broadcasts for various situations.
*/
public final class BroadcastHelper {
private static final boolean DEBUG_BROADCASTS = false;
/**
* Permissions required in order to receive instant application lifecycle broadcasts.
*/
private static final String[] INSTANT_APP_BROADCAST_PERMISSION =
new String[]{android.Manifest.permission.ACCESS_INSTANT_APPS};
private final UserManagerInternal mUmInternal;
private final ActivityManagerInternal mAmInternal;
private final Context mContext;
BroadcastHelper(PackageManagerServiceInjector injector) {
mUmInternal = injector.getUserManagerInternal();
mAmInternal = injector.getActivityManagerInternal();
mContext = injector.getContext();
}
public void sendPackageBroadcast(final String action, final String pkg, final Bundle extras,
final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
final int[] userIds, int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
try {
final IActivityManager am = ActivityManager.getService();
if (am == null) return;
final int[] resolvedUserIds;
if (userIds == null) {
resolvedUserIds = am.getRunningUserIds();
} else {
resolvedUserIds = userIds;
}
if (ArrayUtils.isEmpty(instantUserIds)) {
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
resolvedUserIds, false /* isInstantApp */, broadcastAllowList,
filterExtrasForReceiver, bOptions);
} else {
// send restricted broadcasts for instant apps
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
instantUserIds, true /* isInstantApp */, null,
null /* filterExtrasForReceiver */, bOptions);
}
} catch (RemoteException ex) {
}
}
/**
* Sends a broadcast for the given action.
* <p>If {@code isInstantApp} is {@code true}, then the broadcast is protected with
* the {@link android.Manifest.permission#ACCESS_INSTANT_APPS} permission. This allows
* the system and applications allowed to see instant applications to receive package
* lifecycle events for instant applications.
*/
public void doSendBroadcast(String action, String pkg, Bundle extras,
int flags, String targetPkg, IIntentReceiver finishedReceiver,
int[] userIds, boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
for (int userId : userIds) {
final Intent intent = new Intent(action,
pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null);
final String[] requiredPermissions =
isInstantApp ? INSTANT_APP_BROADCAST_PERMISSION : null;
if (extras != null) {
intent.putExtras(extras);
}
if (targetPkg != null) {
intent.setPackage(targetPkg);
}
// Modify the UID when posting to other users
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (uid >= 0 && UserHandle.getUserId(uid) != userId) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
intent.putExtra(Intent.EXTRA_UID, uid);
}
if (broadcastAllowList != null && PLATFORM_PACKAGE_NAME.equals(targetPkg)) {
intent.putExtra(Intent.EXTRA_VISIBILITY_ALLOW_LIST,
broadcastAllowList.get(userId));
}
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags);
if (DEBUG_BROADCASTS) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.d(TAG, "Sending to user " + userId + ": "
+ intent.toShortString(false, true, false, false)
+ " " + intent.getExtras(), here);
}
final boolean ordered;
if (mAmInternal.isModernQueueEnabled()) {
// When the modern broadcast stack is enabled, deliver all our
// broadcasts as unordered, since the modern stack has better
// support for sequencing cold-starts, and it supports
// delivering resultTo for non-ordered broadcasts
ordered = false;
} else {
ordered = (finishedReceiver != null);
}
mAmInternal.broadcastIntent(
intent, finishedReceiver, requiredPermissions, ordered, userId,
broadcastAllowList == null ? null : broadcastAllowList.get(userId),
filterExtrasForReceiver, bOptions);
}
}
public void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotComputer,
boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames,
@NonNull int[] uids) {
if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) {
return;
}
Bundle extras = new Bundle();
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames);
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
if (replacing) {
extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
}
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
null /* targetPkg */, null /* finishedReceiver */, null /* userIds */,
null /* instantUserIds */, null /* broadcastAllowList */,
(callingUid, intentExtras) -> filterExtrasChangedPackageList(
snapshotComputer.get(), callingUid, intentExtras),
null /* bOptions */);
}
/**
* The just-installed/enabled app is bundled on the system, so presumed to be able to run
* automatically without needing an explicit launch.
* Send it a LOCKED_BOOT_COMPLETED/BOOT_COMPLETED if it would ordinarily have gotten ones.
*/
public void sendBootCompletedBroadcastToSystemApp(
String packageName, boolean includeStopped, int userId) {
// If user is not running, the app didn't miss any broadcast
if (!mUmInternal.isUserRunning(userId)) {
return;
}
final IActivityManager am = ActivityManager.getService();
try {
// Deliver LOCKED_BOOT_COMPLETED first
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.setPackage(packageName);
if (includeStopped) {
lockedBcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
}
final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED};
final BroadcastOptions bOptions = getTemporaryAppAllowlistBroadcastOptions(
REASON_LOCKED_BOOT_COMPLETED);
am.broadcastIntentWithFeature(null, null, lockedBcIntent, null, null, 0, null, null,
requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE,
bOptions.toBundle(), false, false, userId);
// Deliver BOOT_COMPLETED only if user is unlocked
if (mUmInternal.isUserUnlockingOrUnlocked(userId)) {
Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED).setPackage(packageName);
if (includeStopped) {
bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
}
am.broadcastIntentWithFeature(null, null, bcIntent, null, null, 0, null, null,
requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE,
bOptions.toBundle(), false, false, userId);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
public @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@PowerExemptionManager.ReasonCode int reasonCode) {
long duration = 10_000;
if (mAmInternal != null) {
duration = mAmInternal.getBootTimeTempAllowListDuration();
}
final BroadcastOptions bOptions = BroadcastOptions.makeBasic();
bOptions.setTemporaryAppAllowlist(duration,
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
reasonCode, "");
return bOptions;
}
public void sendPackageChangedBroadcast(String packageName, boolean dontKillApp,
ArrayList<String> componentNames, int packageUid, String reason,
int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) {
if (DEBUG_INSTALL) {
Log.v(TAG, "Sending package changed: package=" + packageName + " components="
+ componentNames);
}
Bundle extras = new Bundle(4);
extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0));
String[] nameList = new String[componentNames.size()];
componentNames.toArray(nameList);
extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, dontKillApp);
extras.putInt(Intent.EXTRA_UID, packageUid);
if (reason != null) {
extras.putString(Intent.EXTRA_REASON, reason);
}
// If this is not reporting a change of the overall package, then only send it
// to registered receivers. We don't want to launch a swath of apps for every
// little component state change.
final int flags = !componentNames.contains(packageName)
? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */,
null /* bOptions */);
}
public static void sendDeviceCustomizationReadyBroadcast() {
final Intent intent = new Intent(Intent.ACTION_DEVICE_CUSTOMIZATION_READY);
intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
final IActivityManager am = ActivityManager.getService();
final String[] requiredPermissions = {
Manifest.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY,
};
try {
am.broadcastIntentWithFeature(null, null, intent, null, null, 0, null, null,
requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE, null, false,
false, UserHandle.USER_ALL);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId,
int launcherUid, @Nullable ComponentName launcherComponent,
@Nullable String appPredictionServicePackage) {
if (launcherComponent != null) {
Intent launcherIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED)
.putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
.setPackage(launcherComponent.getPackageName());
mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUid));
}
// TODO(b/122900055) Change/Remove this and replace with new permission role.
if (appPredictionServicePackage != null) {
Intent predictorIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED)
.putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
.setPackage(appPredictionServicePackage);
mContext.sendBroadcastAsUser(predictorIntent, UserHandle.of(launcherUid));
}
}
public void sendPreferredActivityChangedBroadcast(int userId) {
final IActivityManager am = ActivityManager.getService();
if (am == null) {
return;
}
final Intent intent = new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
try {
am.broadcastIntentWithFeature(null, null, intent, null, null,
0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE,
null, false, false, userId);
} catch (RemoteException e) {
}
}
public void sendPackageAddedForNewUsers(String packageName,
@AppIdInt int appId, int[] userIds, int[] instantUserIds,
int dataLoaderType, SparseArray<int[]> broadcastAllowlist) {
Bundle extras = new Bundle(1);
// Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast
final int uid = UserHandle.getUid(
(ArrayUtils.isEmpty(userIds) ? instantUserIds[0] : userIds[0]), appId);
extras.putInt(Intent.EXTRA_UID, uid);
extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, 0, null, null, userIds, instantUserIds,
broadcastAllowlist, null /* filterExtrasForReceiver */, null);
// Send to PermissionController for all new users, even if it may not be running for some
// users
if (isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, 0,
mContext.getPackageManager().getPermissionControllerPackageName(),
null, userIds, instantUserIds,
broadcastAllowlist, null /* filterExtrasForReceiver */, null);
}
}
public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
int[] userIds, int[] instantUserIds) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
null /* filterExtrasForReceiver */, null);
}
/**
* Filter package names for the intent extras {@link Intent#EXTRA_CHANGED_PACKAGE_LIST} and
* {@link Intent#EXTRA_CHANGED_UID_LIST} by using the rules of the package visibility.
*
* @param callingUid The uid that is going to access the intent extras.
* @param extras The intent extras to filter
* @return An extras that have been filtered, or {@code null} if the given uid is unable to
* access all the packages in the extras.
*/
@Nullable
public static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
@NonNull Bundle extras) {
if (UserHandle.isCore(callingUid)) {
// see all
return extras;
}
final String[] pkgs = extras.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST);
if (ArrayUtils.isEmpty(pkgs)) {
return extras;
}
final int userId = extras.getInt(
Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(callingUid));
final int[] uids = extras.getIntArray(Intent.EXTRA_CHANGED_UID_LIST);
final Pair<String[], int[]> filteredPkgs =
filterPackages(snapshot, pkgs, uids, callingUid, userId);
if (ArrayUtils.isEmpty(filteredPkgs.first)) {
// caller is unable to access this intent
return null;
}
final Bundle filteredExtras = new Bundle(extras);
filteredExtras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, filteredPkgs.first);
filteredExtras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, filteredPkgs.second);
return filteredExtras;
}
/** Returns whether the Safety Label Change notification, a privacy feature, is enabled. */
public static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) {
PackageManager packageManager = context.getPackageManager();
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true)
&& !packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
&& !packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
&& !packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
}
@NonNull
private static Pair<String[], int[]> filterPackages(@NonNull Computer snapshot,
@NonNull String[] pkgs, @Nullable int[] uids, int callingUid, int userId) {
final int pkgSize = pkgs.length;
final int uidSize = !ArrayUtils.isEmpty(uids) ? uids.length : 0;
final ArrayList<String> pkgList = new ArrayList<>(pkgSize);
final IntArray uidList = uidSize > 0 ? new IntArray(uidSize) : null;
for (int i = 0; i < pkgSize; i++) {
final String packageName = pkgs[i];
if (snapshot.shouldFilterApplication(
snapshot.getPackageStateInternal(packageName), callingUid, userId)) {
continue;
}
pkgList.add(packageName);
if (uidList != null && i < uidSize) {
uidList.add(uids[i]);
}
}
return new Pair<>(
pkgList.size() > 0 ? pkgList.toArray(new String[pkgList.size()]) : null,
uidList != null && uidList.size() > 0 ? uidList.toArray() : null);
}
}