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

185 lines
8.5 KiB
Java

/*
* Copyright (C) 2022 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.SEND_SAFETY_CENTER_UPDATE;
import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.os.PowerExemptionManager.REASON_REFRESH_SAFETY_SOURCES;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_GET_DATA;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK;
import static android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_STATIC;
import android.annotation.NonNull;
import android.app.BroadcastOptions;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.safetycenter.SafetyCenterManager.RefreshReason;
import android.safetycenter.config.SafetyCenterConfig;
import android.safetycenter.config.SafetySource;
import android.util.Log;
import androidx.annotation.RequiresApi;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Class to manage and track refresh broadcasts sent by {@link SafetyCenterService}.
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
@RequiresApi(TIRAMISU)
final class SafetyCenterRefreshManager {
private static final String TAG = "SafetyCenterRefreshMana";
/**
* Time for which an app, upon receiving a particular broadcast, will be placed on a temporary
* power allowlist allowing it to start a foreground service from the background.
*/
// TODO(b/219553295): Use a Device Config value instead, so that this duration can be
// easily adjusted.
private static final Duration ALLOWLIST_DURATION = Duration.ofSeconds(20);
@NonNull private final List<String> mAdditionalSafetySourcePackageNames = new ArrayList<>();
@NonNull private final Context mContext;
@NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
/**
* Creates a {@link SafetyCenterRefreshManager} using the given {@link Context} and {@link
* SafetyCenterConfigReader}.
*/
SafetyCenterRefreshManager(
@NonNull Context context, @NonNull SafetyCenterConfigReader safetyCenterConfigReader) {
mContext = context;
mSafetyCenterConfigReader = safetyCenterConfigReader;
}
/** Adds a package name representing a source to refresh. */
// TODO(b/218157907): Remove this method and use a SafetyCenterConfigReader field in
// SafetyCenterRefreshManager instead once ag/16834483 is submitted.
void addAdditionalSafetySourcePackageNames(@NonNull String packageName) {
mAdditionalSafetySourcePackageNames.add(packageName);
}
/** Removes all additional package names representing sources to refresh. */
// TODO(b/218157907): Remove this method and use a SafetyCenterConfigReader field in
// SafetyCenterRefreshManager instead once ag/16834483 is submitted.
void clearAdditionalSafetySourcePackageNames() {
mAdditionalSafetySourcePackageNames.clear();
}
/**
* Triggers a refresh of safety sources by sending them broadcasts with action {@link
* android.safetycenter.SafetyCenterManager#ACTION_REFRESH_SAFETY_SOURCES}.
*/
void refreshSafetySources(@RefreshReason int refreshReason) {
int requestType;
switch (refreshReason) {
case REFRESH_REASON_RESCAN_BUTTON_CLICK:
requestType = EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA;
break;
case REFRESH_REASON_PAGE_OPEN:
requestType = EXTRA_REFRESH_REQUEST_TYPE_GET_DATA;
break;
default:
throw new IllegalArgumentException("Invalid refresh reason: " + refreshReason);
}
SafetyCenterConfig safetyCenterConfig = mSafetyCenterConfigReader.getSafetyCenterConfig();
if (safetyCenterConfig == null) {
Log.w(TAG, "SafetyCenterConfig unavailable, ignoring refresh");
return;
}
// TODO(b/219702252): Use a more efficient data structure for this.
List<SafetySource> safetySourcesToRefresh =
safetyCenterConfig.getSafetySourcesGroups().stream()
.flatMap(group -> group.getSafetySources().stream())
.filter(
// Only send broadcasts to dynamic safety sources.
source -> source.getType() != SAFETY_SOURCE_TYPE_STATIC)
.collect(Collectors.toList());
sendRefreshBroadcastToSafetySources(safetySourcesToRefresh, requestType);
sendRefreshBroadcastToAdditionalSafetySourceReceivers(requestType);
}
private void sendRefreshBroadcastToSafetySources(
List<SafetySource> safetySources, int requestType) {
Intent broadcastIntent =
new Intent(ACTION_REFRESH_SAFETY_SOURCES)
.putExtra(EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE, requestType)
.setFlags(FLAG_RECEIVER_FOREGROUND);
BroadcastOptions broadcastOptions = BroadcastOptions.makeBasic();
// The following operation requires START_FOREGROUND_SERVICES_FROM_BACKGROUND
// permission.
broadcastOptions.setTemporaryAppAllowlist(
ALLOWLIST_DURATION.toMillis(),
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
REASON_REFRESH_SAFETY_SOURCES,
"Safety Center is requesting data from safety sources");
for (SafetySource source : safetySources) {
Intent broadcastIntentForSource =
new Intent(broadcastIntent).setPackage(source.getPackageName());
// TODO(b/215144069): Add cross profile support for safety sources which support
// both personal and work profile. This implementation invokes
// `sendBroadcastAsUser` in order to invoke the permission.
// The following operation requires INTERACT_ACROSS_USERS permission.
mContext.sendBroadcastAsUser(
broadcastIntentForSource,
UserHandle.CURRENT,
SEND_SAFETY_CENTER_UPDATE,
broadcastOptions.toBundle());
}
}
private void sendRefreshBroadcastToAdditionalSafetySourceReceivers(int requestType) {
Intent broadcastIntent =
new Intent(ACTION_REFRESH_SAFETY_SOURCES)
.putExtra(EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE, requestType)
.setFlags(FLAG_RECEIVER_FOREGROUND);
BroadcastOptions broadcastOptions = BroadcastOptions.makeBasic();
// The following operation requires START_FOREGROUND_SERVICES_FROM_BACKGROUND
// permission.
broadcastOptions.setTemporaryAppAllowlist(
ALLOWLIST_DURATION.toMillis(),
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
REASON_REFRESH_SAFETY_SOURCES,
"Safety Center is requesting data from safety sources");
for (String packageName : mAdditionalSafetySourcePackageNames) {
// The following operation requires INTERACT_ACROSS_USERS permission.
mContext.sendBroadcastAsUser(
new Intent(broadcastIntent).setPackage(packageName),
UserHandle.CURRENT,
SEND_SAFETY_CENTER_UPDATE,
broadcastOptions.toBundle());
}
}
}