diff options
12 files changed, 791 insertions, 173 deletions
diff --git a/framework-s/java/android/safetycenter/SafetySourceIssue.java b/framework-s/java/android/safetycenter/SafetySourceIssue.java index de75aa298..b6e291fe3 100644 --- a/framework-s/java/android/safetycenter/SafetySourceIssue.java +++ b/framework-s/java/android/safetycenter/SafetySourceIssue.java @@ -956,6 +956,8 @@ public final class SafetySourceIssue implements Parcelable { mConfirmationDialogDetails = action.mConfirmationDialogDetails; } + // TODO(b/303443020): Add setters for id, label, and pendingIntent + /** * Sets whether the action will resolve the safety issue. Defaults to {@code false}. * diff --git a/service/java/com/android/safetycenter/PendingIntentFactory.java b/service/java/com/android/safetycenter/PendingIntentFactory.java index a857a07cb..c4e9decd2 100644 --- a/service/java/com/android/safetycenter/PendingIntentFactory.java +++ b/service/java/com/android/safetycenter/PendingIntentFactory.java @@ -71,7 +71,7 @@ public final class PendingIntentFactory { * is no valid target for the given {@code intentAction}. */ @Nullable - PendingIntent getPendingIntent( + public PendingIntent getPendingIntent( String sourceId, @Nullable String intentAction, String packageName, diff --git a/service/java/com/android/safetycenter/SafetyCenterFlags.java b/service/java/com/android/safetycenter/SafetyCenterFlags.java index 821987ce9..67c4d25d6 100644 --- a/service/java/com/android/safetycenter/SafetyCenterFlags.java +++ b/service/java/com/android/safetycenter/SafetyCenterFlags.java @@ -32,6 +32,9 @@ import com.android.safetycenter.resources.SafetyCenterResourcesApk; import java.io.PrintWriter; import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; /** * A class to access the Safety Center {@link DeviceConfig} flags. @@ -102,6 +105,9 @@ public final class SafetyCenterFlags { private static final String PROPERTY_TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_MILLIS = "safety_center_temp_hidden_issue_resurface_delay_millis"; + private static final String PROPERTY_ACTIONS_TO_OVERRIDE_WITH_DEFAULT_INTENT = + "safety_center_actions_to_override_with_default_intent"; + private static final Duration RESOLVING_ACTION_TIMEOUT_DEFAULT_DURATION = Duration.ofSeconds(10); @@ -127,6 +133,8 @@ public final class SafetyCenterFlags { private static volatile String sRefreshOnPageOpenSourcesDefault = "AndroidBiometrics"; + private static volatile String sActionsToOverrideWithDefaultIntentDefault = ""; + static void init(SafetyCenterResourcesApk safetyCenterResourcesApk) { String untrackedSourcesDefault = safetyCenterResourcesApk.getOptionalStringByName("config_defaultUntrackedSources"); @@ -151,6 +159,12 @@ public final class SafetyCenterFlags { if (refreshOnPageOpenSourcesDefault != null) { sRefreshOnPageOpenSourcesDefault = refreshOnPageOpenSourcesDefault; } + String actionsToOverrideWithDefaultIntentDefault = + safetyCenterResourcesApk.getOptionalStringByName( + "config_defaultActionsToOverrideWithDefaultIntent"); + if (actionsToOverrideWithDefaultIntentDefault != null) { + sActionsToOverrideWithDefaultIntentDefault = actionsToOverrideWithDefaultIntentDefault; + } } private static final Duration TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_DEFAULT_DURATION = @@ -395,6 +409,17 @@ public final class SafetyCenterFlags { return getString(PROPERTY_RESURFACE_ISSUE_DELAYS_MILLIS, RESURFACE_ISSUE_DELAYS_DEFAULT); } + /** + * Returns a comma-delimited list of colon-delimited pairs of SourceId:ActionId. The action IDs + * listed by this flag should have their {@code PendingIntent}s overridden with the source's + * default intent drawn from Safety Center's config file, if available. + */ + private static String getActionsToOverrideWithDefaultIntent() { + return getString( + PROPERTY_ACTIONS_TO_OVERRIDE_WITH_DEFAULT_INTENT, + sActionsToOverrideWithDefaultIntentDefault); + } + /** Returns a duration after which a temporarily hidden issue will resurface. */ public static Duration getTemporarilyHiddenIssueResurfaceDelay() { return getDuration( @@ -408,19 +433,10 @@ public final class SafetyCenterFlags { */ public static boolean isIssueCategoryAllowedForSource( @SafetySourceIssue.IssueCategory int issueCategory, String safetySourceId) { - String issueCategoryAllowlists = getIssueCategoryAllowlists(); - String allowlistString = - getStringValueFromStringMapping(issueCategoryAllowlists, issueCategory); - if (allowlistString == null) { - return true; - } - String[] allowlistArray = allowlistString.split("\\|"); - for (int i = 0; i < allowlistArray.length; i++) { - if (allowlistArray[i].equals(safetySourceId)) { - return true; - } - } - return false; + List<String> allowlist = + getStringListValueFromStringMapping( + getIssueCategoryAllowlists(), Integer.toString(issueCategory)); + return allowlist.isEmpty() || allowlist.contains(safetySourceId); } /** Returns a set of package certificates allowlisted for the given package name. */ @@ -452,6 +468,16 @@ public final class SafetyCenterFlags { } /** + * Returns a list of action IDs that should be overridden with the source's default intent drawn + * from the config for a given source. + */ + public static List<String> getActionsToOverrideWithDefaultIntentForSource( + String safetySourceId) { + return getStringListValueFromStringMapping( + getActionsToOverrideWithDefaultIntent(), safetySourceId); + } + + /** * Returns whether to show subpages in the Safety Center UI for Android-U instead of the * expand-and-collapse list implementation. */ @@ -515,15 +541,15 @@ public final class SafetyCenterFlags { * pairs of integers and longs. */ @Nullable - private static Long getLongValueFromStringMapping(String config, int key) { - String valueString = getStringValueFromStringMapping(config, key); + private static Long getLongValueFromStringMapping(String mapping, int key) { + String valueString = getStringValueFromStringMapping(mapping, key); if (valueString == null) { return null; } try { return Long.parseLong(valueString); } catch (NumberFormatException e) { - Log.w(TAG, "Badly formatted string config: " + config, e); + Log.w(TAG, "Badly formatted string mapping: " + mapping, e); return null; } } @@ -533,8 +559,8 @@ public final class SafetyCenterFlags { * of integers and strings. */ @Nullable - private static String getStringValueFromStringMapping(String config, int key) { - return getStringValueFromStringMapping(config, Integer.toString(key)); + private static String getStringValueFromStringMapping(String mapping, int key) { + return getStringValueFromStringMapping(mapping, Integer.toString(key)); } /** @@ -542,15 +568,15 @@ public final class SafetyCenterFlags { * string pairs. */ @Nullable - private static String getStringValueFromStringMapping(String config, String key) { - if (config.isEmpty()) { + private static String getStringValueFromStringMapping(String mapping, String key) { + if (mapping.isEmpty()) { return null; } - String[] pairsList = config.split(","); + String[] pairsList = mapping.split(","); for (int i = 0; i < pairsList.length; i++) { String[] pair = pairsList[i].split(":", -1 /* allow trailing empty strings */); if (pair.length != 2) { - Log.w(TAG, "Badly formatted string config: " + config); + Log.w(TAG, "Badly formatted string mapping: " + mapping); continue; } if (pair[0].equals(key)) { @@ -560,5 +586,14 @@ public final class SafetyCenterFlags { return null; } + private static List<String> getStringListValueFromStringMapping(String mapping, String key) { + String value = getStringValueFromStringMapping(mapping, key); + if (value == null) { + return Collections.emptyList(); + } + + return Arrays.asList(value.split("\\|")); + } + private SafetyCenterFlags() {} } diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java index 98e97a26c..62d0a0d78 100644 --- a/service/java/com/android/safetycenter/SafetyCenterService.java +++ b/service/java/com/android/safetycenter/SafetyCenterService.java @@ -76,9 +76,9 @@ import com.android.modules.utils.BackgroundThread; import com.android.modules.utils.build.SdkLevel; import com.android.permission.util.ForegroundThread; import com.android.permission.util.UserUtils; -import com.android.safetycenter.data.AndroidLockScreenFix; import com.android.safetycenter.data.SafetyCenterDataManager; import com.android.safetycenter.data.SafetyEventFix; +import com.android.safetycenter.data.SafetySourceDataFix; import com.android.safetycenter.internaldata.SafetyCenterIds; import com.android.safetycenter.internaldata.SafetyCenterIssueActionId; import com.android.safetycenter.internaldata.SafetyCenterIssueId; @@ -120,6 +120,8 @@ public final class SafetyCenterService extends SystemService { @GuardedBy("mApiLock") private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker; + private final SafetySourceDataFix mSafetySourceDataFix; + @GuardedBy("mApiLock") private final SafetyCenterDataManager mSafetyCenterDataManager; @@ -151,6 +153,10 @@ public final class SafetyCenterService extends SystemService { mSafetyCenterResourcesApk = new SafetyCenterResourcesApk(context); mSafetyCenterConfigReader = new SafetyCenterConfigReader(mSafetyCenterResourcesApk); mSafetyCenterRefreshTracker = new SafetyCenterRefreshTracker(context); + PendingIntentFactory pendingIntentFactory = + new PendingIntentFactory(context, mSafetyCenterResourcesApk); + mSafetySourceDataFix = + new SafetySourceDataFix(context, pendingIntentFactory, mSafetyCenterConfigReader); mSafetyCenterDataManager = new SafetyCenterDataManager( context, mSafetyCenterConfigReader, mSafetyCenterRefreshTracker, mApiLock); @@ -160,7 +166,7 @@ public final class SafetyCenterService extends SystemService { mSafetyCenterResourcesApk, mSafetyCenterConfigReader, mSafetyCenterRefreshTracker, - new PendingIntentFactory(context, mSafetyCenterResourcesApk), + pendingIntentFactory, mSafetyCenterDataManager); mSafetyCenterListeners = new SafetyCenterListeners(mSafetyCenterDataFactory); mNotificationChannels = new SafetyCenterNotificationChannels(mSafetyCenterResourcesApk); @@ -310,8 +316,8 @@ public final class SafetyCenterService extends SystemService { UserProfileGroup userProfileGroup = UserProfileGroup.fromUser(getContext(), userId); synchronized (mApiLock) { safetySourceData = - AndroidLockScreenFix.maybeOverrideSafetySourceData( - getContext(), safetySourceId, safetySourceData); + mSafetySourceDataFix.maybeOverrideSafetySourceData( + safetySourceId, safetySourceData, packageName, userId); safetyEvent = SafetyEventFix.maybeOverrideSafetyEvent( mSafetyCenterDataManager, diff --git a/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java b/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java index e46ba2f4d..53043c0f8 100644 --- a/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java +++ b/service/java/com/android/safetycenter/data/AndroidLockScreenFix.java @@ -27,8 +27,6 @@ import android.safetycenter.SafetySourceIssue; import android.safetycenter.SafetySourceStatus; import android.util.Log; -import androidx.annotation.Nullable; - import com.android.modules.utils.build.SdkLevel; import com.android.safetycenter.PendingIntentFactory; import com.android.safetycenter.SafetyCenterFlags; @@ -38,10 +36,8 @@ import java.util.List; /** * A class to work around an issue with the {@code AndroidLockScreen} safety source, by potentially * overriding its {@link SafetySourceData}. - * - * @hide */ -public final class AndroidLockScreenFix { +final class AndroidLockScreenFix { private static final String TAG = "AndroidLockScreenFix"; @@ -55,9 +51,22 @@ public final class AndroidLockScreenFix { private AndroidLockScreenFix() {} + static boolean shouldApplyFix(String sourceId) { + if (SdkLevel.isAtLeastU()) { + // No need to override on U+ as the issue has been fixed in a T QPR release. + // As such, U+ fields for the SafetySourceData are not taken into account in the methods + // below. + return false; + } + if (!ANDROID_LOCK_SCREEN_SOURCE_ID.equals(sourceId)) { + return false; + } + return SafetyCenterFlags.getReplaceLockScreenIconAction(); + } + /** - * Potentially overrides the {@link SafetySourceData} of the {@code AndroidLockScreen} source by - * replacing its {@link PendingIntent}s. + * Overrides the {@link SafetySourceData} of the {@code AndroidLockScreen} source by replacing + * its {@link PendingIntent}s. * * <p>This is done because of a bug in the Settings app where the {@link PendingIntent}s created * end up referencing either the {@link SafetyCenterEntry#getPendingIntent()} or the {@link @@ -70,67 +79,45 @@ public final class AndroidLockScreenFix { * different request codes for the different {@link PendingIntent}s to ensure new instances are * created (the key does take into account the request code). */ - @Nullable - public static SafetySourceData maybeOverrideSafetySourceData( - Context context, String sourceId, @Nullable SafetySourceData safetySourceData) { - if (safetySourceData == null) { - return null; - } - if (SdkLevel.isAtLeastU()) { - // No need to override on U+ as the issue has been fixed in a T QPR release. - // As such, U+ fields for the SafetySourceData are not taken into account in the methods - // below. - return safetySourceData; - } - if (!ANDROID_LOCK_SCREEN_SOURCE_ID.equals(sourceId)) { - return safetySourceData; - } - if (!SafetyCenterFlags.getReplaceLockScreenIconAction()) { - return safetySourceData; - } - return overrideTiramisuSafetySourceData(context, safetySourceData); - } + static SafetySourceData applyFix(Context context, SafetySourceData data) { + SafetySourceData.Builder overriddenData = + SafetySourceDataOverrides.copyDataToBuilderWithoutIssues(data); - private static SafetySourceData overrideTiramisuSafetySourceData( - Context context, SafetySourceData safetySourceData) { - SafetySourceData.Builder overriddenSafetySourceData = new SafetySourceData.Builder(); - SafetySourceStatus safetySourceStatus = safetySourceData.getStatus(); - if (safetySourceStatus != null) { - overriddenSafetySourceData.setStatus( - overrideTiramisuSafetySourceStatus(context, safetySourceStatus)); + SafetySourceStatus originalStatus = data.getStatus(); + if (originalStatus != null) { + overriddenData.setStatus(overrideTiramisuSafetySourceStatus(context, originalStatus)); } - List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues(); - for (int i = 0; i < safetySourceIssues.size(); i++) { - SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i); - overriddenSafetySourceData.addIssue( - overrideTiramisuSafetySourceIssue(context, safetySourceIssue)); + + List<SafetySourceIssue> issues = data.getIssues(); + for (int i = 0; i < issues.size(); i++) { + overriddenData.addIssue(overrideTiramisuIssue(context, issues.get(i))); } - return overriddenSafetySourceData.build(); + + return overriddenData.build(); } private static SafetySourceStatus overrideTiramisuSafetySourceStatus( - Context context, SafetySourceStatus safetySourceStatus) { - SafetySourceStatus.Builder overriddenSafetySourceStatus = - new SafetySourceStatus.Builder( - safetySourceStatus.getTitle(), - safetySourceStatus.getSummary(), - safetySourceStatus.getSeverityLevel()) - .setPendingIntent( - overridePendingIntent( - context, - safetySourceStatus.getPendingIntent(), - /* isIconAction= */ false)) - .setEnabled(safetySourceStatus.isEnabled()); - SafetySourceStatus.IconAction iconAction = safetySourceStatus.getIconAction(); + Context context, SafetySourceStatus status) { + SafetySourceStatus.Builder overriddenStatus = + SafetySourceDataOverrides.copyStatusToBuilder(status); + + PendingIntent originalPendingIntent = status.getPendingIntent(); + if (originalPendingIntent != null) { + overriddenStatus.setPendingIntent( + overridePendingIntent( + context, originalPendingIntent, /* isIconAction= */ false)); + } + + SafetySourceStatus.IconAction iconAction = status.getIconAction(); if (iconAction != null) { - overriddenSafetySourceStatus.setIconAction( - overrideTiramisuSafetySourceStatusIconAction( - context, safetySourceStatus.getIconAction())); + overriddenStatus.setIconAction( + overrideTiramisuIconAction(context, status.getIconAction())); } - return overriddenSafetySourceStatus.build(); + + return overriddenStatus.build(); } - private static SafetySourceStatus.IconAction overrideTiramisuSafetySourceStatusIconAction( + private static SafetySourceStatus.IconAction overrideTiramisuIconAction( Context context, SafetySourceStatus.IconAction iconAction) { return new SafetySourceStatus.IconAction( iconAction.getIconType(), @@ -138,45 +125,30 @@ public final class AndroidLockScreenFix { context, iconAction.getPendingIntent(), /* isIconAction= */ true)); } - private static SafetySourceIssue overrideTiramisuSafetySourceIssue( - Context context, SafetySourceIssue safetySourceIssue) { - SafetySourceIssue.Builder overriddenSafetySourceIssue = - new SafetySourceIssue.Builder( - safetySourceIssue.getId(), - safetySourceIssue.getTitle(), - safetySourceIssue.getSummary(), - safetySourceIssue.getSeverityLevel(), - safetySourceIssue.getIssueTypeId()) - .setSubtitle(safetySourceIssue.getSubtitle()) - .setIssueCategory(safetySourceIssue.getIssueCategory()) - .setOnDismissPendingIntent(safetySourceIssue.getOnDismissPendingIntent()); - List<SafetySourceIssue.Action> actions = safetySourceIssue.getActions(); + private static SafetySourceIssue overrideTiramisuIssue( + Context context, SafetySourceIssue issue) { + SafetySourceIssue.Builder overriddenIssue = + SafetySourceDataOverrides.copyIssueToBuilderWithoutActions(issue); + + List<SafetySourceIssue.Action> actions = issue.getActions(); for (int i = 0; i < actions.size(); i++) { SafetySourceIssue.Action action = actions.get(i); - overriddenSafetySourceIssue.addAction( - overrideTiramisuSafetySourceIssueAction(context, action)); + overriddenIssue.addAction(overrideTiramisuIssueAction(context, action)); } - return overriddenSafetySourceIssue.build(); + + return overriddenIssue.build(); } - private static SafetySourceIssue.Action overrideTiramisuSafetySourceIssueAction( + private static SafetySourceIssue.Action overrideTiramisuIssueAction( Context context, SafetySourceIssue.Action action) { - return new SafetySourceIssue.Action.Builder( - action.getId(), - action.getLabel(), - overridePendingIntent( - context, action.getPendingIntent(), /* isIconAction= */ false)) - .setWillResolve(action.willResolve()) - .setSuccessMessage(action.getSuccessMessage()) - .build(); + PendingIntent pendingIntent = + overridePendingIntent( + context, action.getPendingIntent(), /* isIconAction= */ false); + return SafetySourceDataOverrides.overrideActionPendingIntent(action, pendingIntent); } - @Nullable private static PendingIntent overridePendingIntent( - Context context, @Nullable PendingIntent pendingIntent, boolean isIconAction) { - if (pendingIntent == null) { - return null; - } + Context context, PendingIntent pendingIntent, boolean isIconAction) { String settingsPackageName = pendingIntent.getCreatorPackage(); int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); Context settingsPackageContext = diff --git a/service/java/com/android/safetycenter/data/DefaultActionOverrideFix.java b/service/java/com/android/safetycenter/data/DefaultActionOverrideFix.java new file mode 100644 index 000000000..9ca188670 --- /dev/null +++ b/service/java/com/android/safetycenter/data/DefaultActionOverrideFix.java @@ -0,0 +1,180 @@ +/* + * 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. + * + * <p>Actions to be replaced are controlled by the {@code + * safety_center_actions_to_override_with_default_intent} DeviceConfig flag. + * + * <p>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<String> 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<String> 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<SafetySourceIssue> 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<String> actionsToOverride, PendingIntent defaultIntent) { + SafetySourceIssue.Builder overriddenIssue = + SafetySourceDataOverrides.copyIssueToBuilderWithoutActions(issue); + + List<SafetySourceIssue.Action> 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<String> actionsToOverride, + PendingIntent defaultIntent) { + if (notification == null) { + return null; + } + + SafetySourceIssue.Notification.Builder overriddenNotification = + new SafetySourceIssue.Notification.Builder(notification).clearActions(); + + List<SafetySourceIssue.Action> 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<String> actionsToOverride, + PendingIntent defaultIntent) { + if (actionsToOverride.contains(action.getId())) { + return SafetySourceDataOverrides.overrideActionPendingIntent(action, defaultIntent); + } + return action; + } +} diff --git a/service/java/com/android/safetycenter/data/SafetySourceDataFix.java b/service/java/com/android/safetycenter/data/SafetySourceDataFix.java new file mode 100644 index 000000000..a34f3b03b --- /dev/null +++ b/service/java/com/android/safetycenter/data/SafetySourceDataFix.java @@ -0,0 +1,76 @@ +/* + * 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 android.annotation.UserIdInt; +import android.content.Context; +import android.safetycenter.SafetySourceData; + +import androidx.annotation.Nullable; + +import com.android.safetycenter.PendingIntentFactory; +import com.android.safetycenter.SafetyCenterConfigReader; + +/** + * Applies various workarounds and fixes to {@link SafetySourceData} as it's received. + * + * @hide + */ +public final class SafetySourceDataFix { + + private final DefaultActionOverrideFix mDefaultActionOverrideFix; + private Context mContext; + + public SafetySourceDataFix( + Context context, + PendingIntentFactory pendingIntentFactory, + SafetyCenterConfigReader safetyCenterConfigReader) { + mContext = context; + mDefaultActionOverrideFix = + new DefaultActionOverrideFix( + context, pendingIntentFactory, safetyCenterConfigReader); + } + + /** + * Potentially overrides the {@link SafetySourceData}. + * + * <p>Should be called when the data is received from a source and before it's stored by Safety + * Center. + */ + @Nullable + public SafetySourceData maybeOverrideSafetySourceData( + String sourceId, + @Nullable SafetySourceData safetySourceData, + String packageName, + @UserIdInt int userId) { + if (safetySourceData == null) { + return null; + } + + if (AndroidLockScreenFix.shouldApplyFix(sourceId)) { + safetySourceData = AndroidLockScreenFix.applyFix(mContext, safetySourceData); + } + + if (DefaultActionOverrideFix.shouldApplyFix(sourceId)) { + safetySourceData = + mDefaultActionOverrideFix.applyFix( + sourceId, safetySourceData, packageName, userId); + } + + return safetySourceData; + } +} diff --git a/service/java/com/android/safetycenter/data/SafetySourceDataOverrides.java b/service/java/com/android/safetycenter/data/SafetySourceDataOverrides.java new file mode 100644 index 000000000..b292ae6cb --- /dev/null +++ b/service/java/com/android/safetycenter/data/SafetySourceDataOverrides.java @@ -0,0 +1,81 @@ +/* + * 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 android.app.PendingIntent; +import android.safetycenter.SafetySourceData; +import android.safetycenter.SafetySourceIssue; +import android.safetycenter.SafetySourceStatus; + +import com.android.modules.utils.build.SdkLevel; + +final class SafetySourceDataOverrides { + private SafetySourceDataOverrides() {} + + static SafetySourceData.Builder copyDataToBuilderWithoutIssues(SafetySourceData data) { + if (SdkLevel.isAtLeastU()) { + return new SafetySourceData.Builder(data).clearIssues(); + } + + // Copy T-only fields + return new SafetySourceData.Builder().setStatus(data.getStatus()); + } + + static SafetySourceStatus.Builder copyStatusToBuilder(SafetySourceStatus status) { + if (SdkLevel.isAtLeastU()) { + return new SafetySourceStatus.Builder(status); + } + + // Copy T-only fields + return new SafetySourceStatus.Builder( + status.getTitle(), status.getSummary(), status.getSeverityLevel()) + .setPendingIntent(status.getPendingIntent()) + .setEnabled(status.isEnabled()) + .setIconAction(status.getIconAction()); + } + + static SafetySourceIssue.Builder copyIssueToBuilderWithoutActions(SafetySourceIssue issue) { + if (SdkLevel.isAtLeastU()) { + return new SafetySourceIssue.Builder(issue).clearActions(); + } + + // Copy T-only fields + return new SafetySourceIssue.Builder( + issue.getId(), + issue.getTitle(), + issue.getSummary(), + issue.getSeverityLevel(), + issue.getIssueTypeId()) + .setIssueCategory(issue.getIssueCategory()) + .setSubtitle(issue.getSubtitle()) + .setOnDismissPendingIntent(issue.getOnDismissPendingIntent()); + } + + /** + * Returns an new {@link SafetySourceIssue.Action} object, replacing its {@link PendingIntent} + * with the one supplied. + */ + static SafetySourceIssue.Action overrideActionPendingIntent( + SafetySourceIssue.Action action, PendingIntent pendingIntent) { + // TODO(b/303443020): Add setter for pendingIntent so this method can use the copy builder. + return new SafetySourceIssue.Action.Builder( + action.getId(), action.getLabel(), pendingIntent) + .setWillResolve(action.willResolve()) + .setSuccessMessage(action.getSuccessMessage()) + .build(); + } +} diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt index 19922e4f7..d90fbcd1e 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt @@ -61,7 +61,6 @@ import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC import androidx.test.core.app.ApplicationProvider.getApplicationContext import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress -import com.android.compatibility.common.preconditions.ScreenLockHelper import com.android.compatibility.common.util.SystemUtil import com.android.modules.utils.build.SdkLevel import com.android.safetycenter.internaldata.SafetyCenterBundles @@ -137,7 +136,6 @@ import java.time.Duration import kotlin.test.assertFailsWith import kotlinx.coroutines.TimeoutCancellationException import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -3734,46 +3732,6 @@ class SafetyCenterManagerTest { } @Test - fun lockScreenSource_withoutReplaceLockScreenIconActionFlag_doesntReplace() { - // Must have a screen lock for the icon action to be set - assumeTrue(ScreenLockHelper.isDeviceSecure(context)) - safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.settingsLockScreenSourceConfig) - val listener = safetyCenterTestHelper.addListener() - SafetyCenterFlags.replaceLockScreenIconAction = false - - safetyCenterManager.refreshSafetySourcesWithPermission(REFRESH_REASON_PAGE_OPEN) - // Skip loading data. - listener.receiveSafetyCenterData() - - val lockScreenSafetyCenterData = listener.receiveSafetyCenterData() - val lockScreenEntry = lockScreenSafetyCenterData.entriesOrGroups.first().entry!! - val entryPendingIntent = lockScreenEntry.pendingIntent!! - val iconActionPendingIntent = lockScreenEntry.iconAction!!.pendingIntent - // This test passes for now but will eventually start failing once we introduce the fix in - // the Settings app. This will warn if the assumption is failed rather than fail, at which - // point we can remove this test (and potentially even this magnificent hack). - assumeTrue(iconActionPendingIntent == entryPendingIntent) - } - - @Test - fun lockScreenSource_withReplaceLockScreenIconActionFlag_replaces() { - // Must have a screen lock for the icon action to be set - assumeTrue(ScreenLockHelper.isDeviceSecure(context)) - safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.settingsLockScreenSourceConfig) - val listener = safetyCenterTestHelper.addListener() - - safetyCenterManager.refreshSafetySourcesWithPermission(REFRESH_REASON_PAGE_OPEN) - // Skip loading data. - listener.receiveSafetyCenterData() - - val lockScreenSafetyCenterData = listener.receiveSafetyCenterData() - val lockScreenEntry = lockScreenSafetyCenterData.entriesOrGroups.first().entry!! - val entryPendingIntent = lockScreenEntry.pendingIntent!! - val iconActionPendingIntent = lockScreenEntry.iconAction!!.pendingIntent - assertThat(iconActionPendingIntent).isNotEqualTo(entryPendingIntent) - } - - @Test fun beforeAnyDataSet_noLastUpdatedTimestamps() { safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetySourceDataFixesTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetySourceDataFixesTest.kt new file mode 100644 index 000000000..4ba293eb9 --- /dev/null +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetySourceDataFixesTest.kt @@ -0,0 +1,298 @@ +package android.safetycenter.functional + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE +import android.safetycenter.SafetyCenterManager +import android.safetycenter.SafetySourceData +import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION +import android.safetycenter.SafetySourceIssue +import android.safetycenter.SafetySourceStatus +import androidx.annotation.RequiresApi +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SdkSuppress +import com.android.compatibility.common.preconditions.ScreenLockHelper +import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetySourceDataWithPermission +import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.refreshSafetySourcesWithPermission +import com.android.safetycenter.testing.SafetyCenterFlags +import com.android.safetycenter.testing.SafetyCenterTestConfigs +import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID +import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1 +import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_2 +import com.android.safetycenter.testing.SafetyCenterTestHelper +import com.android.safetycenter.testing.SafetyCenterTestRule +import com.android.safetycenter.testing.SafetySourceTestData +import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity +import com.android.safetycenter.testing.SupportsSafetyCenterRule +import com.google.common.truth.Truth.assertThat +import org.junit.Assume.assumeTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** Functional tests for "fixes" applied to Safety Source data received by [SafetyCenterManager]. */ +@RunWith(AndroidJUnit4::class) +class SafetySourceDataFixesTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + private val safetyCenterTestHelper = SafetyCenterTestHelper(context) + private val safetySourceTestData = SafetySourceTestData(context) + private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context) + private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!! + + @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context) + @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) + + @Test + fun lockScreenSource_withoutReplaceLockScreenIconActionFlag_doesntReplace() { + // Must have a screen lock for the icon action to be set + assumeTrue(ScreenLockHelper.isDeviceSecure(context)) + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.settingsLockScreenSourceConfig) + val listener = safetyCenterTestHelper.addListener() + SafetyCenterFlags.replaceLockScreenIconAction = false + + safetyCenterManager.refreshSafetySourcesWithPermission( + SafetyCenterManager.REFRESH_REASON_PAGE_OPEN + ) + // Skip loading data. + listener.receiveSafetyCenterData() + + val lockScreenSafetyCenterData = listener.receiveSafetyCenterData() + val lockScreenEntry = lockScreenSafetyCenterData.entriesOrGroups.first().entry!! + val entryPendingIntent = lockScreenEntry.pendingIntent!! + val iconActionPendingIntent = lockScreenEntry.iconAction!!.pendingIntent + // This test passes for now but will eventually start failing once we introduce the fix in + // the Settings app. This will warn if the assumption is failed rather than fail, at which + // point we can remove this test (and potentially even this magnificent hack). + assumeTrue(iconActionPendingIntent == entryPendingIntent) + } + + @Test + fun lockScreenSource_withReplaceLockScreenIconActionFlag_replaces() { + // Must have a screen lock for the icon action to be set + assumeTrue(ScreenLockHelper.isDeviceSecure(context)) + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.settingsLockScreenSourceConfig) + val listener = safetyCenterTestHelper.addListener() + + safetyCenterManager.refreshSafetySourcesWithPermission( + SafetyCenterManager.REFRESH_REASON_PAGE_OPEN + ) + // Skip loading data. + listener.receiveSafetyCenterData() + + val lockScreenSafetyCenterData = listener.receiveSafetyCenterData() + val lockScreenEntry = lockScreenSafetyCenterData.entriesOrGroups.first().entry!! + val entryPendingIntent = lockScreenEntry.pendingIntent!! + val iconActionPendingIntent = lockScreenEntry.iconAction!!.pendingIntent + assertThat(iconActionPendingIntent).isNotEqualTo(entryPendingIntent) + } + + @Test + fun defaultActionOverride_issue_overridesMatchingActions() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) + val targetActionId = "TargetActionId" + SafetyCenterFlags.actionsToOverrideWithDefaultIntent = + mapOf(SINGLE_SOURCE_ID to setOf(targetActionId, "AdditionalActionId")) + + val originalPendingIntent = pendingIntent(Intent("blah.wrong.INTENT")) + val dataWithActionToOverride = + sourceDataBuilder() + .addIssue( + issueBuilder() + .clearActions() + .addAction( + safetySourceTestData.action( + id = targetActionId, + pendingIntent = originalPendingIntent + ) + ) + .build() + ) + .build() + + safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, dataWithActionToOverride) + + val overriddenPendingIntent = + safetyCenterManager + .getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)!! + .issues[0] + .actions[0] + .pendingIntent + val expectedPendingIntent = + pendingIntent( + Intent(SafetyCenterTestConfigs.ACTION_TEST_ACTIVITY).setPackage(context.packageName) + ) + assertThat(intentsFilterEqual(overriddenPendingIntent, expectedPendingIntent)).isTrue() + } + @Test + @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE) + fun defaultActionOverride_notification_overridesMatchingActions() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) + val targetActionId = "TargetActionId" + SafetyCenterFlags.actionsToOverrideWithDefaultIntent = + mapOf(SINGLE_SOURCE_ID to setOf(targetActionId, "AdditionalActionId")) + + val originalPendingIntent = pendingIntent(Intent("blah.wrong.INTENT")) + val dataWithNotificationActionToOverride = + sourceDataBuilder() + .addIssue( + issueBuilder() + .setCustomNotification( + notification( + safetySourceTestData.action( + id = targetActionId, + pendingIntent = originalPendingIntent + ) + ) + ) + .build() + ) + .build() + + safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, dataWithNotificationActionToOverride) + + val overriddenPendingIntent = + safetyCenterManager + .getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)!! + .issues[0] + .customNotification!! + .actions[0] + .pendingIntent + val expectedPendingIntent = + pendingIntent( + Intent(SafetyCenterTestConfigs.ACTION_TEST_ACTIVITY).setPackage(context.packageName) + ) + assertThat(intentsFilterEqual(overriddenPendingIntent, expectedPendingIntent)).isTrue() + } + + @Test + fun defaultActionOverride_sameActionIdDifferentSource_doesNotOverride() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig) + val targetActionId = "TargetActionId" + SafetyCenterFlags.actionsToOverrideWithDefaultIntent = + mapOf(SOURCE_ID_1 to setOf(targetActionId, "AdditionalActionId")) + + val originalPendingIntent = pendingIntent(Intent("blah.wrong.INTENT")) + val dataWithoutActionToOverride = + sourceDataBuilder() + .addIssue( + issueBuilder() + .clearActions() + .addAction( + safetySourceTestData.action( + id = targetActionId, + pendingIntent = originalPendingIntent + ) + ) + .build() + ) + .build() + + safetyCenterTestHelper.setData( + SOURCE_ID_2, // Different source ID + dataWithoutActionToOverride + ) + + val actualPendingIntent = + safetyCenterManager + .getSafetySourceDataWithPermission(SOURCE_ID_2)!! + .issues[0] + .actions[0] + .pendingIntent + assertThat(intentsFilterEqual(actualPendingIntent, originalPendingIntent)).isTrue() + } + + @Test + fun defaultActionOverride_sameSourceDifferentActionId_doesNotOverride() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig) + SafetyCenterFlags.actionsToOverrideWithDefaultIntent = + mapOf(SOURCE_ID_1 to setOf("TargetActionId")) + + val originalPendingIntent = pendingIntent(Intent("blah.wrong.INTENT")) + val dataWithoutActionToOverride = + sourceDataBuilder() + .addIssue( + issueBuilder() + .clearActions() + .addAction( + safetySourceTestData.action( + id = "DifferentActionId", + pendingIntent = originalPendingIntent + ) + ) + .build() + ) + .build() + + safetyCenterTestHelper.setData(SOURCE_ID_1, dataWithoutActionToOverride) + + val actualPendingIntent = + safetyCenterManager + .getSafetySourceDataWithPermission(SOURCE_ID_1)!! + .issues[0] + .actions[0] + .pendingIntent + assertThat(intentsFilterEqual(actualPendingIntent, originalPendingIntent)).isTrue() + } + + @Test + fun defaultActionOverride_noDefaultIntent_doesNotOverride() { + safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceInvalidIntentConfig) + val targetActionId = "TargetActionId" + SafetyCenterFlags.actionsToOverrideWithDefaultIntent = + mapOf(SINGLE_SOURCE_ID to setOf(targetActionId, "AdditionalActionId")) + + val originalPendingIntent = pendingIntent(Intent("blah.wrong.INTENT")) + val dataWithActionToOverride = + sourceDataBuilder() + .addIssue( + issueBuilder() + .clearActions() + .addAction( + safetySourceTestData.action( + id = targetActionId, + pendingIntent = originalPendingIntent + ) + ) + .build() + ) + .build() + + safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, dataWithActionToOverride) + + val actualPendingIntent = + safetyCenterManager + .getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)!! + .issues[0] + .actions[0] + .pendingIntent + assertThat(intentsFilterEqual(actualPendingIntent, originalPendingIntent)).isTrue() + } + + private fun issueBuilder() = safetySourceTestData.defaultInformationIssueBuilder() + + private fun pendingIntent(intent: Intent) = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + + companion object { + private fun sourceDataBuilder() = + SafetySourceData.Builder() + .setStatus( + SafetySourceStatus.Builder("OK", "Blah", SEVERITY_LEVEL_INFORMATION).build() + ) + + @RequiresApi(UPSIDE_DOWN_CAKE) + private fun notification(action: SafetySourceIssue.Action) = + SafetySourceIssue.Notification.Builder("Blah", "Bleh").addAction(action).build() + + private fun intentsFilterEqual( + actualPendingIntent: PendingIntent, + expectedPendingIntent: PendingIntent? + ) = + callWithShellPermissionIdentity("android.permission.GET_INTENT_SENDER_INTENT") { + actualPendingIntent.intentFilterEquals(expectedPendingIntent) + } + } +} diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt index 714565d62..912ea44ad 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt @@ -203,6 +203,18 @@ object SafetyCenterFlags { ) /** + * Flag containing a map (a comma separated list of colon separated pairs) where the key is a + * Safety Source ID and the value is a vertical-bar-delimited list of Action IDs that should + * have their PendingIntent replaced with the source's default PendingIntent. + */ + private val actionsToOverrideWithDefaultIntentFlag = + Flag( + "safety_center_actions_to_override_with_default_intent", + defaultValue = emptyMap(), + MapParser(StringParser(), SetParser(StringParser(), delimiter = "|")) + ) + + /** * Flag that represents a comma delimited list of IDs of sources that should only be refreshed * when Safety Center is on screen. We will refresh these sources only on page open and when the * scan button is clicked. @@ -303,6 +315,7 @@ object SafetyCenterFlags { resurfaceIssueMaxCountsFlag, resurfaceIssueDelaysFlag, issueCategoryAllowlistsFlag, + actionsToOverrideWithDefaultIntentFlag, allowedAdditionalPackageCertsFlag, backgroundRefreshDeniedSourcesFlag, allowStatsdLoggingFlag, @@ -358,6 +371,10 @@ object SafetyCenterFlags { /** A property that allows getting and setting the [issueCategoryAllowlistsFlag]. */ var issueCategoryAllowlists: Map<Int, Set<String>> by issueCategoryAllowlistsFlag + /** A property that allows getting and setting the [actionsToOverrideWithDefaultIntentFlag]. */ + var actionsToOverrideWithDefaultIntent: Map<String, Set<String>> by + actionsToOverrideWithDefaultIntentFlag + var allowedAdditionalPackageCerts: Map<String, Set<String>> by allowedAdditionalPackageCertsFlag /** A property that allows getting and setting the [backgroundRefreshDeniedSourcesFlag]. */ diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt index 0b2a6c840..2c4f856bb 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt @@ -114,14 +114,14 @@ class SafetySourceTestData(private val context: Context) { summary: String = "Information issue summary" ) = SafetySourceIssue.Builder(id, title, summary, SEVERITY_LEVEL_INFORMATION, ISSUE_TYPE_ID) - .addAction( - Action.Builder( - INFORMATION_ISSUE_ACTION_ID, - "Review", - createTestActivityRedirectPendingIntent() - ) - .build() - ) + .addAction(action()) + + /** Creates an action with some defaults set. */ + fun action( + id: String = INFORMATION_ISSUE_ACTION_ID, + label: String = "Review", + pendingIntent: PendingIntent = createTestActivityRedirectPendingIntent() + ) = Action.Builder(id, label, pendingIntent).build() /** * A [SafetySourceIssue] with a [SEVERITY_LEVEL_INFORMATION] and a redirecting [Action]. With @@ -136,14 +136,7 @@ class SafetySourceTestData(private val context: Context) { ISSUE_TYPE_ID ) .setSubtitle("Information issue subtitle") - .addAction( - Action.Builder( - INFORMATION_ISSUE_ACTION_ID, - "Review", - createTestActivityRedirectPendingIntent() - ) - .build() - ) + .addAction(action()) .build() /** |