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