blob: b404410badb53f002518b7ad6b2c5b22b8da929c [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 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.annotation.UserIdInt;
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.net.Uri;
import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 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 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, bOptions);
} else {
// send restricted broadcasts for instant apps
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
instantUserIds, true /* isInstantApp */, null, 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 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);
}
mAmInternal.broadcastIntent(
intent, finishedReceiver, requiredPermissions,
finishedReceiver != null, userId,
broadcastAllowList == null ? null : broadcastAllowList.get(userId),
bOptions);
}
}
public void sendResourcesChangedBroadcast(@NonNull Computer snapshot, boolean mediaStatus,
boolean replacing, @NonNull String[] pkgNames, @NonNull int[] uids) {
if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) {
return;
}
try {
final IActivityManager am = ActivityManager.getService();
if (am == null) {
return;
}
final int[] resolvedUserIds = am.getRunningUserIds();
for (int userId : resolvedUserIds) {
final var lists = getBroadcastParams(snapshot, pkgNames, uids, userId);
for (int i = 0; i < lists.size(); i++) {
// Send broadcasts here
final Bundle extras = new Bundle(3);
final BroadcastParams list = lists.get(i);
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
list.getPackageNames());
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
final SparseArray<int[]> allowList = list.getAllowList().size() == 0
? null : list.getAllowList();
final String action =
mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
null /* targetPkg */, null /* finishedReceiver */, new int[]{userId},
null /* instantUserIds */, allowList, null /* bOptions */);
}
}
} catch (RemoteException ex) {
}
}
/**
* 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, 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, 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);
}
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, 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, 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);
}
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);
}
/**
* Get broadcast params list based on the given package and uid list. The broadcast params are
* used to send broadcast separately if the given packages have different visibility allow list.
*
* @param pkgList The names of packages which have changes.
* @param uidList The uids of packages which have changes.
* @param userId The user where packages reside.
* @return The list of {@link BroadcastParams} object.
*/
public List<BroadcastParams> getBroadcastParams(@NonNull Computer snapshot,
@NonNull String[] pkgList, @NonNull int[] uidList, @UserIdInt int userId) {
final List<BroadcastParams> lists = new ArrayList<>(pkgList.length);
// Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
// allow lists are the same.
for (int i = 0; i < pkgList.length; i++) {
final String pkgName = pkgList[i];
final int uid = uidList[i];
if (TextUtils.isEmpty(pkgName) || Process.INVALID_UID == uid) {
continue;
}
int[] allowList = snapshot.getVisibilityAllowList(pkgName, userId);
if (allowList == null) {
allowList = new int[0];
}
boolean merged = false;
for (int j = 0; j < lists.size(); j++) {
final BroadcastParams list = lists.get(j);
if (Arrays.equals(list.getAllowList().get(userId), allowList)) {
list.addPackage(pkgName, uid);
merged = true;
break;
}
}
if (!merged) {
lists.add(new BroadcastParams(pkgName, uid, allowList, userId));
}
}
return lists;
}
}