185 lines
8.5 KiB
Java
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());
|
|
}
|
|
}
|
|
}
|