1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.safetycenter.data; 18 19 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 20 21 import android.annotation.UserIdInt; 22 import android.app.PendingIntent; 23 import android.content.Context; 24 import android.safetycenter.SafetySourceData; 25 import android.safetycenter.SafetySourceIssue; 26 27 import androidx.annotation.Nullable; 28 import androidx.annotation.RequiresApi; 29 30 import com.android.modules.utils.build.SdkLevel; 31 import com.android.permission.util.UserUtils; 32 import com.android.safetycenter.PendingIntentFactory; 33 import com.android.safetycenter.SafetyCenterConfigReader; 34 import com.android.safetycenter.SafetyCenterFlags; 35 36 import java.util.List; 37 38 /** 39 * Replaces {@link SafetySourceIssue.Action}s with the corresponding source's default intent drawn 40 * from the Safety Center config. 41 * 42 * <p>Actions to be replaced are controlled by the {@code 43 * safety_center_actions_to_override_with_default_intent} DeviceConfig flag. 44 * 45 * <p>This is done to support cases where we allow OEMs to override intents in the config, but 46 * sources are unaware of and unable to access those overrides when providing issues and 47 * notifications. We use the default intent when sources provide a null pending intent in their 48 * status. This fix allows us to implement a similar behavior for actions, without changing the 49 * non-null requirement on their pending intent fields. 50 */ 51 final class DefaultActionOverrideFix { 52 53 private final Context mContext; 54 private final PendingIntentFactory mPendingIntentFactory; 55 private final SafetyCenterConfigReader mSafetyCenterConfigReader; 56 DefaultActionOverrideFix( Context context, PendingIntentFactory pendingIntentFactory, SafetyCenterConfigReader safetyCenterConfigReader)57 DefaultActionOverrideFix( 58 Context context, 59 PendingIntentFactory pendingIntentFactory, 60 SafetyCenterConfigReader safetyCenterConfigReader) { 61 mContext = context; 62 mPendingIntentFactory = pendingIntentFactory; 63 mSafetyCenterConfigReader = safetyCenterConfigReader; 64 } 65 shouldApplyFix(String sourceId)66 static boolean shouldApplyFix(String sourceId) { 67 List<String> actionsToOverride = 68 SafetyCenterFlags.getActionsToOverrideWithDefaultIntentForSource(sourceId); 69 return !actionsToOverride.isEmpty(); 70 } 71 applyFix( String sourceId, SafetySourceData safetySourceData, String packageName, @UserIdInt int userId)72 SafetySourceData applyFix( 73 String sourceId, 74 SafetySourceData safetySourceData, 75 String packageName, 76 @UserIdInt int userId) { 77 if (safetySourceData.getIssues().isEmpty()) { 78 return safetySourceData; 79 } 80 81 PendingIntent defaultIntentForSource = 82 getDefaultIntentForSource(sourceId, packageName, userId); 83 if (defaultIntentForSource == null) { 84 // If there's no default intent, we can't override any actions with it. 85 return safetySourceData; 86 } 87 88 List<String> actionsToOverride = 89 SafetyCenterFlags.getActionsToOverrideWithDefaultIntentForSource(sourceId); 90 if (actionsToOverride.isEmpty()) { 91 // This shouldn't happen if shouldApplyFix is called first, but we check for good 92 // measure. 93 return safetySourceData; 94 } 95 96 SafetySourceData.Builder overriddenSafetySourceData = 97 SafetySourceDataOverrides.copyDataToBuilderWithoutIssues(safetySourceData); 98 List<SafetySourceIssue> issues = safetySourceData.getIssues(); 99 for (int i = 0; i < issues.size(); i++) { 100 overriddenSafetySourceData.addIssue( 101 maybeOverrideActionsWithDefaultIntent( 102 issues.get(i), actionsToOverride, defaultIntentForSource)); 103 } 104 105 return overriddenSafetySourceData.build(); 106 } 107 108 @Nullable getDefaultIntentForSource( String sourceId, String packageName, @UserIdInt int userId)109 private PendingIntent getDefaultIntentForSource( 110 String sourceId, String packageName, @UserIdInt int userId) { 111 SafetyCenterConfigReader.ExternalSafetySource externalSafetySource = 112 mSafetyCenterConfigReader.getExternalSafetySource(sourceId, packageName); 113 if (externalSafetySource == null) { 114 return null; 115 } 116 117 boolean isQuietModeEnabled = 118 UserUtils.isManagedProfile(userId, mContext) 119 && !UserUtils.isProfileRunning(userId, mContext); 120 121 return mPendingIntentFactory.getPendingIntent( 122 sourceId, 123 externalSafetySource.getSafetySource().getIntentAction(), 124 packageName, 125 userId, 126 isQuietModeEnabled); 127 } 128 maybeOverrideActionsWithDefaultIntent( SafetySourceIssue issue, List<String> actionsToOverride, PendingIntent defaultIntent)129 private SafetySourceIssue maybeOverrideActionsWithDefaultIntent( 130 SafetySourceIssue issue, List<String> actionsToOverride, PendingIntent defaultIntent) { 131 SafetySourceIssue.Builder overriddenIssue = 132 SafetySourceDataOverrides.copyIssueToBuilderWithoutActions(issue); 133 134 List<SafetySourceIssue.Action> actions = issue.getActions(); 135 for (int i = 0; i < actions.size(); i++) { 136 overriddenIssue.addAction( 137 maybeOverrideAction(actions.get(i), actionsToOverride, defaultIntent)); 138 } 139 140 if (SdkLevel.isAtLeastU()) { 141 overriddenIssue.setCustomNotification( 142 maybeOverrideNotification( 143 issue.getCustomNotification(), actionsToOverride, defaultIntent)); 144 } 145 146 return overriddenIssue.build(); 147 } 148 149 @RequiresApi(UPSIDE_DOWN_CAKE) 150 @Nullable maybeOverrideNotification( @ullable SafetySourceIssue.Notification notification, List<String> actionsToOverride, PendingIntent defaultIntent)151 private static SafetySourceIssue.Notification maybeOverrideNotification( 152 @Nullable SafetySourceIssue.Notification notification, 153 List<String> actionsToOverride, 154 PendingIntent defaultIntent) { 155 if (notification == null) { 156 return null; 157 } 158 159 SafetySourceIssue.Notification.Builder overriddenNotification = 160 new SafetySourceIssue.Notification.Builder(notification).clearActions(); 161 162 List<SafetySourceIssue.Action> actions = notification.getActions(); 163 for (int i = 0; i < actions.size(); i++) { 164 overriddenNotification.addAction( 165 maybeOverrideAction(actions.get(i), actionsToOverride, defaultIntent)); 166 } 167 168 return overriddenNotification.build(); 169 } 170 maybeOverrideAction( SafetySourceIssue.Action action, List<String> actionsToOverride, PendingIntent defaultIntent)171 private static SafetySourceIssue.Action maybeOverrideAction( 172 SafetySourceIssue.Action action, 173 List<String> actionsToOverride, 174 PendingIntent defaultIntent) { 175 if (actionsToOverride.contains(action.getId())) { 176 return SafetySourceDataOverrides.overrideActionPendingIntent(action, defaultIntent); 177 } 178 return action; 179 } 180 } 181