/* * 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.os.Build.VERSION_CODES.TIRAMISU; import static android.safetycenter.SafetyCenterManager.RefreshReason; import android.annotation.Nullable; import android.os.Binder; import android.provider.DeviceConfig; import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceIssue; import android.util.ArraySet; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; import com.android.safetycenter.resources.SafetyCenterResourcesContext; import java.io.PrintWriter; import java.time.Duration; /** * A class to access the Safety Center {@link DeviceConfig} flags. * * @hide */ @RequiresApi(TIRAMISU) public final class SafetyCenterFlags { private static final String TAG = "SafetyCenterFlags"; /** {@link DeviceConfig} property name for {@link #getSafetyCenterEnabled()}. */ static final String PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled"; private static final String PROPERTY_NOTIFICATIONS_ENABLED = "safety_center_notifications_enabled"; private static final String PROPERTY_NOTIFICATIONS_ALLOWED_SOURCES = "safety_center_notifications_allowed_sources"; private static final String PROPERTY_NOTIFICATIONS_MIN_DELAY = "safety_center_notifications_min_delay"; private static final String PROPERTY_NOTIFICATIONS_IMMEDIATE_BEHAVIOR_ISSUES = "safety_center_notifications_immediate_behavior_issues"; private static final String PROPERTY_NOTIFICATION_RESURFACE_INTERVAL = "safety_center_notification_resurface_interval"; private static final String PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT = "safety_center_show_error_entries_on_timeout"; private static final String PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION = "safety_center_replace_lock_screen_icon_action"; private static final String PROPERTY_RESOLVING_ACTION_TIMEOUT_MILLIS = "safety_center_resolve_action_timeout_millis"; private static final String PROPERTY_FGS_ALLOWLIST_DURATION_MILLIS = "safety_center_refresh_fgs_allowlist_duration_millis"; private static final String PROPERTY_RESURFACE_ISSUE_MAX_COUNTS = "safety_center_resurface_issue_max_counts"; private static final String PROPERTY_RESURFACE_ISSUE_DELAYS_MILLIS = "safety_center_resurface_issue_delays_millis"; private static final String PROPERTY_UNTRACKED_SOURCES = "safety_center_untracked_sources"; private static final String PROPERTY_BACKGROUND_REFRESH_DENIED_SOURCES = "safety_center_background_refresh_denied_sources"; private static final String PROPERTY_REFRESH_SOURCES_TIMEOUTS_MILLIS = "safety_center_refresh_sources_timeouts_millis"; private static final String PROPERTY_ISSUE_CATEGORY_ALLOWLISTS = "safety_center_issue_category_allowlists"; private static final String PROPERTY_ALLOW_STATSD_LOGGING = "safety_center_allow_statsd_logging"; private static final String PROPERTY_SHOW_SUBPAGES = "safety_center_show_subpages"; private static final String PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES = "safety_center_override_refresh_on_page_open_sources"; private static final String PROPERTY_ADDITIONAL_ALLOW_PACKAGE_CERTS = "safety_center_additional_allow_package_certs"; private static final Duration FGS_ALLOWLIST_DEFAULT_DURATION = Duration.ofSeconds(20); private static final String PROPERTY_TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_MILLIS = "safety_center_temp_hidden_issue_resurface_delay_millis"; private static final Duration RESOLVING_ACTION_TIMEOUT_DEFAULT_DURATION = Duration.ofSeconds(10); private static final Duration NOTIFICATIONS_MIN_DELAY_DEFAULT_DURATION = Duration.ofDays(180); private static final String REFRESH_SOURCES_TIMEOUT_DEFAULT = "100:15000,200:60000,300:30000,400:30000,500:30000,600:3600000"; private static final Duration REFRESH_SOURCES_TIMEOUT_DEFAULT_DURATION = Duration.ofSeconds(15); private static final String RESURFACE_ISSUE_MAX_COUNT_DEFAULT = "200:0,300:1,400:1"; private static final long RESURFACE_ISSUE_MAX_COUNT_DEFAULT_COUNT = 0; private static final String RESURFACE_ISSUE_DELAYS_DEFAULT = ""; private static final Duration RESURFACE_ISSUE_DELAYS_DEFAULT_DURATION = Duration.ofDays(180); private static volatile String sUntrackedSourcesDefault = "AndroidAccessibility,AndroidBackgroundLocation," + "AndroidNotificationListener,AndroidPermissionAutoRevoke"; private static volatile String sBackgroundRefreshDenyDefault = ""; private static volatile String sIssueCategoryAllowlistDefault = ""; private static volatile String sRefreshOnPageOpenSourcesDefault = "AndroidBiometrics,AndroidLockScreen"; static void init(SafetyCenterResourcesContext resourceContext) { String untrackedSourcesDefault = resourceContext.getOptionalStringByName("config_defaultUntrackedSources"); if (untrackedSourcesDefault != null) { sUntrackedSourcesDefault = untrackedSourcesDefault; } String backgroundRefreshDenyDefault = resourceContext.getOptionalStringByName("config_defaultBackgroundRefreshDeny"); if (backgroundRefreshDenyDefault != null) { sBackgroundRefreshDenyDefault = backgroundRefreshDenyDefault; } String issueCategoryAllowlistDefault = resourceContext.getOptionalStringByName("config_defaultIssueCategoryAllowlist"); if (issueCategoryAllowlistDefault != null) { sIssueCategoryAllowlistDefault = issueCategoryAllowlistDefault; } String refreshOnPageOpenSourcesDefault = resourceContext.getOptionalStringByName("config_defaultRefreshOnPageOpenSources"); if (refreshOnPageOpenSourcesDefault != null) { sRefreshOnPageOpenSourcesDefault = refreshOnPageOpenSourcesDefault; } } private static final Duration TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_DEFAULT_DURATION = Duration.ofDays(2); /** Dumps state for debugging purposes. */ static void dump(PrintWriter fout) { fout.println("FLAGS"); printFlag(fout, PROPERTY_SAFETY_CENTER_ENABLED, getSafetyCenterEnabled()); printFlag(fout, PROPERTY_NOTIFICATIONS_ENABLED, getNotificationsEnabled()); printFlag(fout, PROPERTY_NOTIFICATIONS_ALLOWED_SOURCES, getNotificationsAllowedSourceIds()); printFlag(fout, PROPERTY_NOTIFICATIONS_MIN_DELAY, getNotificationsMinDelay()); printFlag( fout, PROPERTY_NOTIFICATIONS_IMMEDIATE_BEHAVIOR_ISSUES, getImmediateNotificationBehaviorIssues()); printFlag( fout, PROPERTY_NOTIFICATION_RESURFACE_INTERVAL, getNotificationResurfaceInterval()); printFlag(fout, PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT, getShowErrorEntriesOnTimeout()); printFlag(fout, PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION, getReplaceLockScreenIconAction()); printFlag(fout, PROPERTY_RESOLVING_ACTION_TIMEOUT_MILLIS, getResolvingActionTimeout()); printFlag(fout, PROPERTY_FGS_ALLOWLIST_DURATION_MILLIS, getFgsAllowlistDuration()); printFlag(fout, PROPERTY_UNTRACKED_SOURCES, getUntrackedSourceIds()); printFlag(fout, PROPERTY_RESURFACE_ISSUE_MAX_COUNTS, getResurfaceIssueMaxCounts()); printFlag(fout, PROPERTY_RESURFACE_ISSUE_DELAYS_MILLIS, getResurfaceIssueDelaysMillis()); printFlag( fout, PROPERTY_BACKGROUND_REFRESH_DENIED_SOURCES, getBackgroundRefreshDeniedSourceIds()); printFlag( fout, PROPERTY_REFRESH_SOURCES_TIMEOUTS_MILLIS, getRefreshSourcesTimeoutsMillis()); printFlag(fout, PROPERTY_ISSUE_CATEGORY_ALLOWLISTS, getIssueCategoryAllowlists()); printFlag(fout, PROPERTY_ALLOW_STATSD_LOGGING, getAllowStatsdLogging()); printFlag(fout, PROPERTY_SHOW_SUBPAGES, getShowSubpages()); printFlag( fout, PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES, getOverrideRefreshOnPageOpenSourceIds()); printFlag( fout, PROPERTY_ADDITIONAL_ALLOW_PACKAGE_CERTS, getAdditionalAllowedPackageCertsString()); fout.println(); } private static void printFlag(PrintWriter pw, String key, @Nullable Duration duration) { if (duration == null) { printFlag(pw, key, "null"); } else { printFlag(pw, key, duration.toMillis() + " (" + duration + ")"); } } private static void printFlag(PrintWriter pw, String key, Object value) { pw.println("\t" + key + "=" + value); } /** Returns whether Safety Center is enabled. */ public static boolean getSafetyCenterEnabled() { return getBoolean(PROPERTY_SAFETY_CENTER_ENABLED, SdkLevel.isAtLeastU()); } /** Returns whether Safety Center notifications are enabled. */ public static boolean getNotificationsEnabled() { return getBoolean(PROPERTY_NOTIFICATIONS_ENABLED, SdkLevel.isAtLeastU()); } /** * Returns the IDs of sources that Safety Center can send notifications about, in addition to * those permitted by the current XML config. * *
If the ID of a source appears on this list then Safety Center may send notifications about * issues from that source, regardless of (overriding) the XML config. If the ID of a source is * absent from this list, then Safety Center may send such notifications only if the XML config * allows it. * *
Note that the {@code areNotificationsAllowed} config attribute is only available on API U+
     * and therefore this is the only way to enable notifications for sources on Android T.
     */
    public static ArraySet The actual delay used may be longer.
     */
    public static Duration getNotificationsMinDelay() {
        return getDuration(
                PROPERTY_NOTIFICATIONS_MIN_DELAY, NOTIFICATIONS_MIN_DELAY_DEFAULT_DURATION);
    }
    /**
     * Returns the issue type IDs for which, if otherwise undefined, Safety Center should use {@link
     * SafetySourceIssue#NOTIFICATION_BEHAVIOR_IMMEDIATELY}.
     *
     *  If a safety source specifies the notification behavior of an issue explicitly this flag
     * has no effect, even if the issue matches one of the entries in this flag.
     *
     *  Entries in this set should be strings of the form "safety_source_id/issue_type_id".
     */
    public static ArraySet Returns {@code null} if the underlying device config flag is either unset or is set to a
     * negative value.
     *
     *  There may be other conditions for resurfacing a notification and the actual delay may be
     * longer than this.
     */
    @Nullable
    public static Duration getNotificationResurfaceInterval() {
        long millis = getLong(PROPERTY_NOTIFICATION_RESURFACE_INTERVAL, -1);
        if (millis < 0) {
            return null;
        } else {
            return Duration.ofMillis(millis);
        }
    }
    /**
     * Returns whether we should show error entries for sources that timeout when refreshing them.
     */
    static boolean getShowErrorEntriesOnTimeout() {
        return getBoolean(PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT, true);
    }
    /**
     * Returns whether we should replace the lock screen source's {@link
     * android.safetycenter.SafetySourceStatus.IconAction}.
     */
    public static boolean getReplaceLockScreenIconAction() {
        return getBoolean(PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION, true);
    }
    /**
     * Returns the time for which Safety Center will wait for a source to respond to a resolving
     * action before timing out.
     */
    static Duration getResolvingActionTimeout() {
        return getDuration(
                PROPERTY_RESOLVING_ACTION_TIMEOUT_MILLIS,
                RESOLVING_ACTION_TIMEOUT_DEFAULT_DURATION);
    }
    /**
     * Returns the time for which an app, upon receiving a Safety Center refresh broadcast, will be
     * placed on a temporary power allowlist allowing it to start a foreground service from the
     * background.
     */
    static Duration getFgsAllowlistDuration() {
        return getDuration(PROPERTY_FGS_ALLOWLIST_DURATION_MILLIS, FGS_ALLOWLIST_DEFAULT_DURATION);
    }
    /**
     * Returns the IDs of sources that should not be tracked, for example because they are
     * mid-rollout. Broadcasts are still sent to these sources.
     */
    static ArraySet