/* * 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.READ_SAFETY_CENTER_STATUS; import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE; import static android.content.Intent.FLAG_INCLUDE_STOPPED_PACKAGES; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; 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.ACTION_SAFETY_CENTER_ENABLED_CHANGED; import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID; import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE; import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS; import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN; import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED; import static java.util.Collections.unmodifiableList; import android.annotation.UserIdInt; import android.app.BroadcastOptions; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.UserHandle; import android.safetycenter.SafetyCenterManager; import android.safetycenter.SafetyCenterManager.RefreshReason; import android.safetycenter.SafetySourceData; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import androidx.annotation.Nullable; import com.android.permission.util.PackageUtils; import com.android.safetycenter.SafetyCenterConfigReader.Broadcast; import com.android.safetycenter.UserProfileGroup.ProfileType; import com.android.safetycenter.data.SafetyCenterDataManager; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import javax.annotation.concurrent.NotThreadSafe; /** * A class that dispatches SafetyCenter broadcasts. * *
This class isn't thread safe. Thread safety must be handled by the caller. */ @NotThreadSafe final class SafetyCenterBroadcastDispatcher { private static final String TAG = "SafetyCenterBroadcastDi"; private final Context mContext; private final SafetyCenterConfigReader mSafetyCenterConfigReader; private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker; private final SafetyCenterDataManager mSafetyCenterDataManager; SafetyCenterBroadcastDispatcher( Context context, SafetyCenterConfigReader safetyCenterConfigReader, SafetyCenterRefreshTracker safetyCenterRefreshTracker, SafetyCenterDataManager safetyCenterDataManager) { mContext = context; mSafetyCenterConfigReader = safetyCenterConfigReader; mSafetyCenterRefreshTracker = safetyCenterRefreshTracker; mSafetyCenterDataManager = safetyCenterDataManager; } /** * Triggers a refresh of safety sources by sending them broadcasts with action {@link * SafetyCenterManager#ACTION_REFRESH_SAFETY_SOURCES}, and returns the associated broadcast id. * *
Returns {@code null} if no broadcast was sent.
*
* @param safetySourceIds list of IDs to specify the safety sources to be refreshed or a {@code
* null} value to refresh all safety sources.
*/
@Nullable
String sendRefreshSafetySources(
@RefreshReason int refreshReason,
UserProfileGroup userProfileGroup,
@Nullable List This method also sends an implicit broadcast globally (which requires the {@link
* android.Manifest.permission#READ_SAFETY_CENTER_STATUS} permission).
*/
// TODO(b/227310195): Consider adding a boolean extra to the intent instead of having clients
// rely on SafetyCenterManager#isSafetyCenterEnabled()?
void sendEnabledChanged() {
List The set of user IDs (keys) is the profile parent user ID of {@code userProfileGroup} plus
* all the other types of running profiles:
* Every value present is a non-empty list, but the overall result may be empty.
*/
private SparseArray For {@link SafetyCenterManager#REFRESH_REASON_PAGE_OPEN}, returns a copy of {@code
* allSourceIds} filtered to contain only sources that have refreshOnPageOpenAllowed in the XML
* config, or are in the safety_center_override_refresh_on_page_open_sources flag, or don't have
* any {@link SafetySourceData} provided.
*/
private List> userIdsToSourceIds =
getUserIdsToSourceIds(broadcast, userProfileGroup, refreshReason);
for (int i = 0; i < userIdsToSourceIds.size(); i++) {
int userId = userIdsToSourceIds.keyAt(i);
List
> userIdsToSourceIds =
getUserIdsToSourceIds(
broadcast,
userProfileGroup,
// The same ENABLED reason is used here for both enable and disable
// events. It is not sent externally and is only used internally to
// filter safety sources in the methods of the Broadcast class.
REFRESH_REASON_SAFETY_CENTER_ENABLED);
for (int j = 0; j < userIdsToSourceIds.size(); j++) {
int userId = userIdsToSourceIds.keyAt(j);
sendBroadcastIfResolves(intent, UserHandle.of(userId), broadcastOptions);
}
}
}
private boolean sendBroadcastIfResolves(
Intent intent, UserHandle userHandle, @Nullable BroadcastOptions broadcastOptions) {
if (!doesBroadcastResolve(intent, userHandle)) {
Log.w(
TAG,
"No receiver for intent targeting: "
+ intent.getPackage()
+ ", and user id: "
+ userHandle.getIdentifier());
return false;
}
Log.v(
TAG,
"Found receiver for intent targeting: "
+ intent.getPackage()
+ ", and user id: "
+ userHandle.getIdentifier());
sendBroadcast(intent, userHandle, SEND_SAFETY_CENTER_UPDATE, broadcastOptions);
return true;
}
private void sendBroadcast(
Intent intent,
UserHandle userHandle,
String permission,
@Nullable BroadcastOptions broadcastOptions) {
// This call requires the INTERACT_ACROSS_USERS permission.
final long callingId = Binder.clearCallingIdentity();
try {
mContext.sendBroadcastAsUser(
intent,
userHandle,
permission,
broadcastOptions == null ? null : broadcastOptions.toBundle());
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
private boolean doesBroadcastResolve(Intent broadcastIntent, UserHandle userHandle) {
return !PackageUtils.queryUnfilteredBroadcastReceiversAsUser(
broadcastIntent, /* flags= */ 0, userHandle.getIdentifier(), mContext)
.isEmpty();
}
private static Intent createExplicitEnabledChangedIntent(String packageName) {
return createImplicitEnabledChangedIntent().setPackage(packageName);
}
private static Intent createImplicitEnabledChangedIntent() {
return createBroadcastIntent(ACTION_SAFETY_CENTER_ENABLED_CHANGED);
}
private static Intent createRefreshIntent(
@RefreshReason int refreshReason,
String packageName,
List
*
* > getUserIdsToSourceIds(
Broadcast broadcast,
UserProfileGroup userProfileGroup,
@RefreshReason int refreshReason) {
SparseArray
> result =
new SparseArray<>(userProfileGroup.getNumRunningProfiles());
for (int profilTypeIdx = 0;
profilTypeIdx < ProfileType.ALL_PROFILE_TYPES.length;
++profilTypeIdx) {
@ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profilTypeIdx];
int[] runningProfiles = userProfileGroup.getRunningProfilesOfType(profileType);
for (int profileIdx = 0; profileIdx < runningProfiles.length; ++profileIdx) {
List