android13/packages/modules/Permission/service/java/com/android/safetycenter/SafetyCenterService.java

440 lines
18 KiB
Java

/*
* 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.safetycenter;
import static android.Manifest.permission.MANAGE_SAFETY_CENTER;
import static android.Manifest.permission.READ_SAFETY_CENTER_STATUS;
import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.safetycenter.SafetyCenterManager.RefreshReason;
import static android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_STATIC;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Binder;
import android.os.RemoteCallbackList;
import android.provider.DeviceConfig;
import android.safetycenter.IOnSafetyCenterDataChangedListener;
import android.safetycenter.ISafetyCenterManager;
import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterErrorDetails;
import android.safetycenter.SafetyEvent;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceErrorDetails;
import android.safetycenter.config.SafetyCenterConfig;
import android.safetycenter.config.SafetySource;
import android.safetycenter.config.SafetySourcesGroup;
import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.GuardedBy;
import com.android.permission.util.PermissionUtils;
import com.android.server.SystemService;
import java.util.Arrays;
/**
* Service for the safety center.
*
* @hide
*/
@Keep
@RequiresApi(TIRAMISU)
public final class SafetyCenterService extends SystemService {
private static final String TAG = "SafetyCenterService";
/** Phenotype flag that determines whether SafetyCenter is enabled. */
private static final String PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled";
private final Object mApiLock = new Object();
// Refresh/rescan is guarded by another lock: sending broadcasts can be a lengthy operation and
// the APIs that will be exercised by the receivers are already protected by `mApiLock`.
private final Object mRefreshLock = new Object();
@GuardedBy("mApiLock")
private final SafetyCenterListeners mSafetyCenterListeners = new SafetyCenterListeners();
@NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
@GuardedBy("mApiLock")
@NonNull
private final SafetyCenterDataTracker mSafetyCenterDataTracker;
@GuardedBy("mRefreshLock")
@NonNull
private final SafetyCenterRefreshManager mSafetyCenterRefreshManager;
@NonNull private final AppOpsManager mAppOpsManager;
public SafetyCenterService(@NonNull Context context) {
super(context);
mSafetyCenterConfigReader = new SafetyCenterConfigReader(context);
mSafetyCenterDataTracker = new SafetyCenterDataTracker(context, mSafetyCenterConfigReader);
mSafetyCenterRefreshManager =
new SafetyCenterRefreshManager(context, mSafetyCenterConfigReader);
mAppOpsManager = requireNonNull(context.getSystemService(AppOpsManager.class));
}
@Override
public void onStart() {
publishBinderService(Context.SAFETY_CENTER_SERVICE, new Stub());
mSafetyCenterConfigReader.loadSafetyCenterConfig();
}
/** Service implementation of {@link ISafetyCenterManager.Stub}. */
private final class Stub extends ISafetyCenterManager.Stub {
@Override
public boolean isSafetyCenterEnabled() {
enforceAnyCallingOrSelfPermissions(
"isSafetyCenterEnabled", READ_SAFETY_CENTER_STATUS, SEND_SAFETY_CENTER_UPDATE);
// TODO(b/214568975): Decide if we should disable safety center if there is a problem
// reading the config.
return isApiEnabled();
}
@Override
public void setSafetySourceData(
@NonNull String safetySourceId,
@Nullable SafetySourceData safetySourceData,
@NonNull SafetyEvent safetyEvent,
@NonNull String packageName,
@UserIdInt int userId) {
mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "setSafetySourceData", getContext());
// TODO(b/205706756): Security: check certs?
getContext()
.enforceCallingOrSelfPermission(
SEND_SAFETY_CENTER_UPDATE, "setSafetySourceData");
if (!checkApiEnabled("setSafetySourceData")) {
return;
}
// TODO(b/218812582): Validate the SafetySourceData.
SafetyCenterData safetyCenterData;
RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners;
synchronized (mApiLock) {
safetyCenterData =
mSafetyCenterDataTracker.setSafetySourceData(
safetySourceId, safetySourceData, packageName, userId);
listeners = mSafetyCenterListeners.getListeners(userId);
}
// This doesn't need to be done while holding the lock, as RemoteCallbackList already
// handles concurrent calls.
// If the listener uses SafetyCenterManager and is executed on #directExecutor(),
// doing this while holding the lock could also potentially lead to deadlocks.
if (listeners != null && safetyCenterData != null) {
// TODO(b/218811189): This should be called on all listeners associated with the
// userId, i.e. if #setSafetySourceData is called with a work profile userId,
// we should also let the personal profile listeners know about the update.
SafetyCenterListeners.deliverUpdate(listeners, safetyCenterData);
}
}
@Override
@Nullable
public SafetySourceData getSafetySourceData(
@NonNull String safetySourceId,
@NonNull String packageName,
@UserIdInt int userId) {
mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "getSafetySourceData", getContext());
// TODO(b/205706756): Security: check certs?
getContext()
.enforceCallingOrSelfPermission(
SEND_SAFETY_CENTER_UPDATE, "getSafetySourceData");
if (!checkApiEnabled("getSafetySourceData")) {
return null;
}
synchronized (mApiLock) {
return mSafetyCenterDataTracker.getSafetySourceData(
safetySourceId, packageName, userId);
}
}
@Override
public void reportSafetySourceError(
@NonNull String safetySourceId,
@NonNull SafetySourceErrorDetails errorDetails,
@NonNull String packageName,
@UserIdInt int userId) {
mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "reportSafetySourceError", getContext());
getContext()
.enforceCallingOrSelfPermission(
SEND_SAFETY_CENTER_UPDATE, "reportSafetySourceError");
if (!checkApiEnabled("reportSafetySourceError")) {
return;
}
// TODO(b/218379298): Add implementation
RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners;
synchronized (mApiLock) {
listeners = mSafetyCenterListeners.getListeners(userId);
}
SafetyCenterListeners.deliverError(listeners, new SafetyCenterErrorDetails("Error"));
}
@Override
public void refreshSafetySources(@RefreshReason int refreshReason, @UserIdInt int userId) {
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "refreshSafetySources", getContext());
getContext().enforceCallingPermission(MANAGE_SAFETY_CENTER, "refreshSafetySources");
if (!checkApiEnabled("refreshSafetySources")) {
return;
}
// We don't require the caller to have INTERACT_ACROSS_USERS and
// START_FOREGROUND_SERVICES_FROM_BACKGROUND permissions.
final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mRefreshLock) {
mSafetyCenterRefreshManager.refreshSafetySources(refreshReason);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
@Override
@Nullable
public SafetyCenterConfig getSafetyCenterConfig() {
getContext()
.enforceCallingOrSelfPermission(MANAGE_SAFETY_CENTER, "getSafetyCenterConfig");
synchronized (mApiLock) {
return mSafetyCenterConfigReader.getSafetyCenterConfig();
}
}
@Override
@NonNull
public SafetyCenterData getSafetyCenterData(@UserIdInt int userId) {
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "getSafetyCenterData", getContext());
getContext()
.enforceCallingOrSelfPermission(MANAGE_SAFETY_CENTER, "getSafetyCenterData");
if (!checkApiEnabled("getSafetyCenterData")) {
return SafetyCenterDataTracker.getDefaultSafetyCenterData();
}
synchronized (mApiLock) {
return mSafetyCenterDataTracker.getSafetyCenterData(userId);
}
}
@Override
public void addOnSafetyCenterDataChangedListener(
@NonNull IOnSafetyCenterDataChangedListener listener, @UserIdInt int userId) {
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "addOnSafetyCenterDataChangedListener", getContext());
getContext()
.enforceCallingOrSelfPermission(
MANAGE_SAFETY_CENTER, "addOnSafetyCenterDataChangedListener");
if (!checkApiEnabled("addOnSafetyCenterDataChangedListener")) {
return;
}
SafetyCenterData safetyCenterData;
synchronized (mApiLock) {
mSafetyCenterListeners.addListener(listener, userId);
safetyCenterData = mSafetyCenterDataTracker.getSafetyCenterData(userId);
}
// This doesn't need to be done while holding the lock.
// If the listener uses SafetyCenterManager and is executed on #directExecutor(),
// doing this while holding the lock could also potentially lead to deadlocks.
SafetyCenterListeners.deliverUpdate(listener, safetyCenterData);
}
@Override
public void removeOnSafetyCenterDataChangedListener(
@NonNull IOnSafetyCenterDataChangedListener listener, @UserIdInt int userId) {
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "removeOnSafetyCenterDataChangedListener", getContext());
getContext()
.enforceCallingOrSelfPermission(
MANAGE_SAFETY_CENTER, "removeOnSafetyCenterDataChangedListener");
if (!checkApiEnabled("removeOnSafetyCenterDataChangedListener")) {
return;
}
synchronized (mApiLock) {
mSafetyCenterListeners.removeListener(listener, userId);
}
}
@Override
public void dismissSafetyCenterIssue(String issueId, @UserIdInt int userId) {
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "dismissSafetyCenterIssue", getContext());
getContext()
.enforceCallingOrSelfPermission(
MANAGE_SAFETY_CENTER, "dismissSafetyCenterIssue");
if (!checkApiEnabled("dismissSafetyCenterIssue")) {
return;
}
// TODO(b/202387059): Implement issue dismissal.
}
@Override
public void executeSafetyCenterIssueAction(
@NonNull String safetyCenterIssueId,
@NonNull String safetyCenterActionId,
@UserIdInt int userId) {
// TODO(b/217235899): Finalize cross-user behavior.
PermissionUtils.enforceCrossUserPermission(
userId, false, "executeSafetyCenterIssueAction", getContext());
getContext()
.enforceCallingOrSelfPermission(
MANAGE_SAFETY_CENTER, "executeSafetyCenterIssueAction");
if (!checkApiEnabled("executeSafetyCenterIssueAction")) {
return;
}
// TODO(b/218379298): Add implementation
}
@Override
public void clearAllSafetySourceDataForTests() {
getContext()
.enforceCallingOrSelfPermission(
MANAGE_SAFETY_CENTER, "clearAllSafetySourceDataForTests");
if (!checkApiEnabled("clearAllSafetySourceDataForTests")) {
return;
}
synchronized (mApiLock) {
mSafetyCenterDataTracker.clear();
}
}
@Override
public void setSafetyCenterConfigForTests(@NonNull SafetyCenterConfig safetyCenterConfig) {
getContext()
.enforceCallingOrSelfPermission(
MANAGE_SAFETY_CENTER, "setSafetyCenterConfigForTests");
if (!checkApiEnabled("setSafetyCenterConfigForTests")) {
return;
}
synchronized (mRefreshLock) {
// TODO(b/217944317): Implement properly by overriding config in
// SafetyCenterConfigReader instead. This placeholder impl serves to allow this
// API to be merged in tm-dev, and final impl will be in tm-mainline-prod.
for (int i = 0; i < safetyCenterConfig.getSafetySourcesGroups().size(); i++) {
SafetySourcesGroup group = safetyCenterConfig.getSafetySourcesGroups().get(i);
for (int j = 0; j < group.getSafetySources().size(); j++) {
SafetySource safetySource = group.getSafetySources().get(j);
if (safetySource.getType() != SAFETY_SOURCE_TYPE_STATIC) {
mSafetyCenterRefreshManager.addAdditionalSafetySourcePackageNames(
safetySource.getPackageName());
}
}
}
}
}
@Override
public void clearSafetyCenterConfigForTests() {
getContext()
.enforceCallingOrSelfPermission(
MANAGE_SAFETY_CENTER, "clearSafetyCenterConfigForTests");
if (!checkApiEnabled("clearSafetyCenterConfigForTests")) {
return;
}
synchronized (mRefreshLock) {
mSafetyCenterRefreshManager.clearAdditionalSafetySourcePackageNames();
}
}
private boolean isApiEnabled() {
return getSafetyCenterConfigValue() && getDeviceConfigSafetyCenterEnabledProperty();
}
private boolean getDeviceConfigSafetyCenterEnabledProperty() {
// This call requires the READ_DEVICE_CONFIG permission.
final long callingId = Binder.clearCallingIdentity();
try {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_SAFETY_CENTER_ENABLED,
/* defaultValue = */ false);
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
private boolean getSafetyCenterConfigValue() {
return getContext()
.getResources()
.getBoolean(
Resources.getSystem()
.getIdentifier("config_enableSafetyCenter", "bool", "android"));
}
private void enforceAnyCallingOrSelfPermissions(
@NonNull String message, String... permissions) {
if (permissions.length == 0) {
throw new IllegalArgumentException("Must check at least one permission");
}
for (int i = 0; i < permissions.length; i++) {
if (getContext().checkCallingOrSelfPermission(permissions[i])
== PackageManager.PERMISSION_GRANTED) {
return;
}
}
throw new SecurityException(
message
+ " requires any of: "
+ Arrays.toString(permissions)
+ ", but none were granted");
}
private boolean checkApiEnabled(@NonNull String message) {
if (!isApiEnabled()) {
Log.w(TAG, String.format("Called %s, but Safety Center is disabled", message));
return false;
}
return true;
}
}
}