/*
* Copyright (C) 2023 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.data;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.Context;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceIssue;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.UserUtils;
import com.android.safetycenter.PendingIntentFactory;
import com.android.safetycenter.SafetyCenterConfigReader;
import com.android.safetycenter.SafetyCenterFlags;
import java.util.List;
/**
* Replaces {@link SafetySourceIssue.Action}s with the corresponding source's default intent drawn
* from the Safety Center config.
*
*
Actions to be replaced are controlled by the {@code
* safety_center_actions_to_override_with_default_intent} DeviceConfig flag.
*
*
This is done to support cases where we allow OEMs to override intents in the config, but
* sources are unaware of and unable to access those overrides when providing issues and
* notifications. We use the default intent when sources provide a null pending intent in their
* status. This fix allows us to implement a similar behavior for actions, without changing the
* non-null requirement on their pending intent fields.
*/
final class DefaultActionOverrideFix {
private final Context mContext;
private final PendingIntentFactory mPendingIntentFactory;
private final SafetyCenterConfigReader mSafetyCenterConfigReader;
DefaultActionOverrideFix(
Context context,
PendingIntentFactory pendingIntentFactory,
SafetyCenterConfigReader safetyCenterConfigReader) {
mContext = context;
mPendingIntentFactory = pendingIntentFactory;
mSafetyCenterConfigReader = safetyCenterConfigReader;
}
static boolean shouldApplyFix(String sourceId) {
List actionsToOverride =
SafetyCenterFlags.getActionsToOverrideWithDefaultIntentForSource(sourceId);
return !actionsToOverride.isEmpty();
}
SafetySourceData applyFix(
String sourceId,
SafetySourceData safetySourceData,
String packageName,
@UserIdInt int userId) {
if (safetySourceData.getIssues().isEmpty()) {
return safetySourceData;
}
PendingIntent defaultIntentForSource =
getDefaultIntentForSource(sourceId, packageName, userId);
if (defaultIntentForSource == null) {
// If there's no default intent, we can't override any actions with it.
return safetySourceData;
}
List actionsToOverride =
SafetyCenterFlags.getActionsToOverrideWithDefaultIntentForSource(sourceId);
if (actionsToOverride.isEmpty()) {
// This shouldn't happen if shouldApplyFix is called first, but we check for good
// measure.
return safetySourceData;
}
SafetySourceData.Builder overriddenSafetySourceData =
SafetySourceDataOverrides.copyDataToBuilderWithoutIssues(safetySourceData);
List issues = safetySourceData.getIssues();
for (int i = 0; i < issues.size(); i++) {
overriddenSafetySourceData.addIssue(
maybeOverrideActionsWithDefaultIntent(
issues.get(i), actionsToOverride, defaultIntentForSource));
}
return overriddenSafetySourceData.build();
}
@Nullable
private PendingIntent getDefaultIntentForSource(
String sourceId, String packageName, @UserIdInt int userId) {
SafetyCenterConfigReader.ExternalSafetySource externalSafetySource =
mSafetyCenterConfigReader.getExternalSafetySource(sourceId, packageName);
if (externalSafetySource == null) {
return null;
}
boolean isQuietModeEnabled =
UserUtils.isManagedProfile(userId, mContext)
&& !UserUtils.isProfileRunning(userId, mContext);
return mPendingIntentFactory.getPendingIntent(
sourceId,
externalSafetySource.getSafetySource().getIntentAction(),
packageName,
userId,
isQuietModeEnabled);
}
private SafetySourceIssue maybeOverrideActionsWithDefaultIntent(
SafetySourceIssue issue, List actionsToOverride, PendingIntent defaultIntent) {
SafetySourceIssue.Builder overriddenIssue =
SafetySourceDataOverrides.copyIssueToBuilderWithoutActions(issue);
List actions = issue.getActions();
for (int i = 0; i < actions.size(); i++) {
overriddenIssue.addAction(
maybeOverrideAction(actions.get(i), actionsToOverride, defaultIntent));
}
if (SdkLevel.isAtLeastU()) {
overriddenIssue.setCustomNotification(
maybeOverrideNotification(
issue.getCustomNotification(), actionsToOverride, defaultIntent));
}
return overriddenIssue.build();
}
@RequiresApi(UPSIDE_DOWN_CAKE)
@Nullable
private static SafetySourceIssue.Notification maybeOverrideNotification(
@Nullable SafetySourceIssue.Notification notification,
List actionsToOverride,
PendingIntent defaultIntent) {
if (notification == null) {
return null;
}
SafetySourceIssue.Notification.Builder overriddenNotification =
new SafetySourceIssue.Notification.Builder(notification).clearActions();
List actions = notification.getActions();
for (int i = 0; i < actions.size(); i++) {
overriddenNotification.addAction(
maybeOverrideAction(actions.get(i), actionsToOverride, defaultIntent));
}
return overriddenNotification.build();
}
private static SafetySourceIssue.Action maybeOverrideAction(
SafetySourceIssue.Action action,
List actionsToOverride,
PendingIntent defaultIntent) {
if (actionsToOverride.contains(action.getId())) {
return SafetySourceDataOverrides.overrideActionPendingIntent(action, defaultIntent);
}
return action;
}
}