/*
 * 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 java.util.Collections.emptyList;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterEntry;
import android.safetycenter.SafetyCenterEntryGroup;
import android.safetycenter.SafetyCenterEntryOrGroup;
import android.safetycenter.SafetyCenterIssue;
import android.safetycenter.SafetyCenterStaticEntry;
import android.safetycenter.SafetyCenterStaticEntryGroup;
import android.safetycenter.SafetyCenterStatus;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceIssue;
import android.safetycenter.SafetySourceStatus;
import android.safetycenter.config.SafetyCenterConfig;
import android.safetycenter.config.SafetySource;
import android.safetycenter.config.SafetySourcesGroup;
import android.util.Log;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
 * A class that keeps track of all the {@link SafetySourceData} set by safety sources, and
 * aggregates them into a {@link SafetyCenterData} object to be used by permission controller.
 *
 * 
This class isn't thread safe. Thread safety must be handled by the caller.
 */
@RequiresApi(TIRAMISU)
final class SafetyCenterDataTracker {
    private static final String TAG = "SafetyCenterDataTracker";
    private final Map mSafetySourceDataForKey = new HashMap<>();
    @NonNull private final Context mContext;
    @NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
    /**
     * Creates a {@link SafetyCenterDataTracker} using the given {@link Context} and {@link
     * SafetyCenterConfigReader}.
     */
    SafetyCenterDataTracker(
            @NonNull Context context, @NonNull SafetyCenterConfigReader safetyCenterConfigReader) {
        mContext = context;
        mSafetyCenterConfigReader = safetyCenterConfigReader;
    }
    /**
     * Sets the latest {@link SafetySourceData} for the given {@code safetySourceId} and {@code
     * userId}, and returns the updated {@link SafetyCenterData} of the {@code userId}.
     *
     * Returns {@code null} if there was no update to the underlying {@link SafetyCenterData}, or
     * if the {@link SafetyCenterConfig} is not available.
     */
    @Nullable
    SafetyCenterData setSafetySourceData(
            @NonNull String safetySourceId,
            @Nullable SafetySourceData safetySourceData,
            @NonNull String packageName,
            @UserIdInt int userId) {
        if (!configContains(safetySourceId, packageName)) {
            // TODO(b/218801292): Should this be hard error for the caller?
            return null;
        }
        Key key = Key.of(safetySourceId, packageName, userId);
        SafetySourceData existingSafetySourceData = mSafetySourceDataForKey.get(key);
        if (Objects.equals(safetySourceData, existingSafetySourceData)) {
            return null;
        }
        mSafetySourceDataForKey.put(key, safetySourceData);
        return getSafetyCenterData(userId);
    }
    /**
     * Returns the latest {@link SafetySourceData} for the given {@code safetySourceId} and {@code
     * userId}.
     *
     * 
Returns {@code null} if there was no data set.
     */
    @Nullable
    SafetySourceData getSafetySourceData(
            @NonNull String safetySourceId, @NonNull String packageName, @UserIdInt int userId) {
        if (!configContains(safetySourceId, packageName)) {
            // TODO(b/218801292): Should this be hard error for the caller?
            return null;
        }
        return mSafetySourceDataForKey.get(Key.of(safetySourceId, packageName, userId));
    }
    /** Clears all the {@link SafetySourceData} set received so far, for all users. */
    void clear() {
        mSafetySourceDataForKey.clear();
    }
    /**
     * Returns the current {@link SafetyCenterData} for the given {@code userId}, aggregated from
     * all the {@link SafetySourceData} set so far.
     *
     * 
Returns an arbitrary default value if no data has been received for the user so far, or if
     * the {@link SafetyCenterConfig} is not available.
     */
    @NonNull
    SafetyCenterData getSafetyCenterData(@UserIdInt int userId) {
        SafetyCenterConfig safetyCenterConfig = mSafetyCenterConfigReader.getSafetyCenterConfig();
        if (safetyCenterConfig == null) {
            Log.w(TAG, "SafetyCenterConfig unavailable, returning default SafetyCenterData");
            return getDefaultSafetyCenterData();
        }
        // TODO(b/218819144): Merge for all profiles.
        return getSafetyCenterData(safetyCenterConfig, userId);
    }
    /**
     * Returns a default {@link SafetyCenterData} object to be returned when the API is disabled.
     */
    @NonNull
    static SafetyCenterData getDefaultSafetyCenterData() {
        return new SafetyCenterData(
                new SafetyCenterStatus.Builder(
                                getSafetyCenterStatusTitle(
                                        SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN),
                                getSafetyCenterStatusSummary(
                                        SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN))
                        .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)
                        .build(),
                emptyList(),
                emptyList(),
                emptyList());
    }
    // TODO(b/219702252): Create a more efficient data structure for this, and update it when the
    //  config changes.
    private boolean configContains(@NonNull String safetySourceId, @NonNull String packageName) {
        SafetyCenterConfig safetyCenterConfig = mSafetyCenterConfigReader.getSafetyCenterConfig();
        if (safetyCenterConfig == null) {
            Log.w(TAG, "SafetyCenterConfig unavailable, assuming no sources can send/get data");
            return false;
        }
        // TODO(b/217944317): Remove this allowlisting once the test API for the config is
        //  available.
        if (packageName.equals("android.safetycenter.cts")) {
            return true;
        }
        List safetySourcesGroups = safetyCenterConfig.getSafetySourcesGroups();
        for (int i = 0; i < safetySourcesGroups.size(); i++) {
            SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i);
            List safetySources = safetySourcesGroup.getSafetySources();
            for (int j = 0; j < safetySources.size(); j++) {
                SafetySource safetySource = safetySources.get(j);
                if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_STATIC) {
                    continue;
                }
                if (safetySourceId.equals(safetySource.getId())
                        && packageName.equals(safetySource.getPackageName())) {
                    return true;
                }
            }
        }
        return false;
    }
    @NonNull
    private SafetyCenterData getSafetyCenterData(
            @NonNull SafetyCenterConfig safetyCenterConfig, @UserIdInt int userId) {
        int maxSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
        List safetyCenterIssues = new ArrayList<>();
        List safetyCenterEntryOrGroups = new ArrayList<>();
        List safetyCenterStaticEntryGroups = new ArrayList<>();
        List safetySourcesGroups = safetyCenterConfig.getSafetySourcesGroups();
        for (int i = 0; i < safetySourcesGroups.size(); i++) {
            SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i);
            int groupSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
            switch (safetySourcesGroup.getType()) {
                case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE: {
                    groupSafetyCenterEntryLevel =
                            Math.max(
                                    addSafetyCenterIssues(
                                            safetyCenterIssues, safetySourcesGroup, userId),
                                    addSafetyCenterEntryGroup(
                                            safetyCenterEntryOrGroups,
                                            safetySourcesGroup,
                                            userId));
                    break;
                }
                case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_RIGID: {
                    addSafetyCenterStaticEntryGroup(
                            safetyCenterStaticEntryGroups, safetySourcesGroup);
                    break;
                }
                case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN: {
                    groupSafetyCenterEntryLevel =
                            addSafetyCenterIssues(
                                    safetyCenterIssues, safetySourcesGroup, userId);
                    break;
                }
            }
            // TODO(b/219700241): Should we rely on ordering for severity levels?
            maxSafetyCenterEntryLevel =
                    Math.max(maxSafetyCenterEntryLevel, groupSafetyCenterEntryLevel);
        }
        int safetyCenterOverallSeverityLevel =
                entryToSafetyCenterStatusOverallLevel(maxSafetyCenterEntryLevel);
        return new SafetyCenterData(
                new SafetyCenterStatus.Builder(
                                getSafetyCenterStatusTitle(safetyCenterOverallSeverityLevel),
                                getSafetyCenterStatusSummary(safetyCenterOverallSeverityLevel))
                        .setSeverityLevel(safetyCenterOverallSeverityLevel)
                        .build(),
                safetyCenterIssues,
                safetyCenterEntryOrGroups,
                safetyCenterStaticEntryGroups);
    }
    @SafetyCenterEntry.EntrySeverityLevel
    private int addSafetyCenterIssues(
            @NonNull List safetyCenterIssues,
            @NonNull SafetySourcesGroup safetySourcesGroup,
            @UserIdInt int userId) {
        int maxSafetyCenterEntrySeverityLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
        List safetySources = safetySourcesGroup.getSafetySources();
        for (int i = 0; i < safetySources.size(); i++) {
            SafetySource safetySource = safetySources.get(i);
            if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_STATIC) {
                continue;
            }
            Key key = Key.of(safetySource.getId(), safetySource.getPackageName(), userId);
            SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
            if (safetySourceData == null) {
                continue;
            }
            List safetySourceIssues = safetySourceData.getIssues();
            for (int j = 0; j < safetySourceIssues.size(); j++) {
                SafetySourceIssue safetySourceIssue = safetySourceIssues.get(j);
                SafetyCenterIssue safetyCenterIssue = toSafetyCenterIssue(safetySourceIssue);
                maxSafetyCenterEntrySeverityLevel =
                        Math.max(
                                maxSafetyCenterEntrySeverityLevel,
                                issueToSafetyCenterEntryLevel(
                                        safetyCenterIssue.getSeverityLevel()));
                safetyCenterIssues.add(safetyCenterIssue);
            }
        }
        return maxSafetyCenterEntrySeverityLevel;
    }
    @NonNull
    private static SafetyCenterIssue toSafetyCenterIssue(
            @NonNull SafetySourceIssue safetySourceIssue) {
        List safetySourceIssueActions = safetySourceIssue.getActions();
        List safetyCenterIssueActions =
                new ArrayList<>(safetySourceIssueActions.size());
        for (int i = 0; i < safetySourceIssueActions.size(); i++) {
            SafetySourceIssue.Action safetySourceIssueAction = safetySourceIssueActions.get(i);
            safetyCenterIssueActions.add(
                    new SafetyCenterIssue.Action.Builder(
                                    safetySourceIssue.getId(),
                                    safetySourceIssueAction.getLabel(),
                                    safetySourceIssueAction.getPendingIntent())
                            .setSuccessMessage(safetySourceIssueAction.getSuccessMessage())
                            .build());
        }
        // TODO(b/218817233): Add missing fields like: dismissible, shouldConfirmDismissal.
        return new SafetyCenterIssue.Builder(
                        safetySourceIssue.getId(),
                        safetySourceIssue.getTitle(),
                        safetySourceIssue.getSummary())
                .setSeverityLevel(
                        sourceToSafetyCenterIssueSeverityLevel(
                                safetySourceIssue.getSeverityLevel()))
                .setSubtitle(safetySourceIssue.getSubtitle())
                .setActions(safetyCenterIssueActions)
                .build();
    }
    @SafetyCenterEntry.EntrySeverityLevel
    private int addSafetyCenterEntryGroup(
            @NonNull List safetyCenterEntryOrGroups,
            @NonNull SafetySourcesGroup safetySourcesGroup,
            @UserIdInt int userId) {
        int maxSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
        List safetySources = safetySourcesGroup.getSafetySources();
        List entries = new ArrayList<>(safetySources.size());
        for (int i = 0; i < safetySources.size(); i++) {
            SafetySource safetySource = safetySources.get(i);
            SafetyCenterEntry safetyCenterEntry = toSafetyCenterEntry(safetySource, userId);
            if (safetyCenterEntry == null) {
                continue;
            }
            // TODO(b/219700241): Should we rely on ordering for severity levels?
            maxSafetyCenterEntryLevel =
                    Math.max(maxSafetyCenterEntryLevel, safetyCenterEntry.getSeverityLevel());
            entries.add(safetyCenterEntry);
        }
        // TODO(b/218817233): Add missing fields like: statelessIconType.
        safetyCenterEntryOrGroups.add(
                new SafetyCenterEntryOrGroup(
                        new SafetyCenterEntryGroup.Builder(
                                        safetySourcesGroup.getId(),
                                        mSafetyCenterConfigReader.readStringResource(
                                                safetySourcesGroup.getTitleResId()))
                                .setSeverityLevel(maxSafetyCenterEntryLevel)
                                .setSummary(
                                        mSafetyCenterConfigReader.readStringResource(
                                                safetySourcesGroup.getSummaryResId()))
                                .setEntries(entries)
                                .build()));
        return maxSafetyCenterEntryLevel;
    }
    @Nullable
    private SafetyCenterEntry toSafetyCenterEntry(
            @NonNull SafetySource safetySource, @UserIdInt int userId) {
        switch (safetySource.getType()) {
            case SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY: {
                Log.w(TAG, "Issue only safety source found in collapsible group");
                return null;
            }
            case SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC: {
                Key key = Key.of(safetySource.getId(), safetySource.getPackageName(), userId);
                SafetySourceStatus safetySourceStatus =
                        getSafetySourceStatus(mSafetySourceDataForKey.get(key));
                // TODO(b/218817233): Add missing fields like: iconAction, statelessIconType.
                if (safetySourceStatus != null) {
                    PendingIntent pendingIntent = safetySourceStatus.getPendingIntent();
                    if (pendingIntent == null) {
                        pendingIntent =
                                toPendingIntent(
                                        safetySource.getIntentAction(),
                                        safetySource.getPackageName());
                        // TODO(b/222838784): Automatically mark the source as disabled if the
                        //  pending intent is null again.
                    }
                    return new SafetyCenterEntry.Builder(
                                    safetySource.getId(), safetySourceStatus.getTitle())
                            .setSeverityLevel(
                                    sourceToSafetyCenterEntrySeverityLevel(
                                            safetySourceStatus.getSeverityLevel()))
                            .setSummary(safetySourceStatus.getSummary())
                            .setEnabled(safetySourceStatus.isEnabled())
                            .setPendingIntent(pendingIntent)
                            .build();
                }
                return toDefaultSafetyCenterEntry(
                        safetySource,
                        safetySource.getPackageName(),
                        SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED);
            }
            case SafetySource.SAFETY_SOURCE_TYPE_STATIC: {
                return toDefaultSafetyCenterEntry(
                        safetySource, null, SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN);
            }
        }
        Log.w(
                TAG,
                String.format(
                        "Unknown safety source type found in collapsible group: %s",
                        safetySource.getType()));
        return null;
    }
    @Nullable
    private SafetyCenterEntry toDefaultSafetyCenterEntry(
            @NonNull SafetySource safetySource,
            @Nullable String packageName,
            @SafetyCenterEntry.EntrySeverityLevel int entrySeverityLevel) {
        if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC
                && safetySource.getInitialDisplayState()
                        == SafetySource.INITIAL_DISPLAY_STATE_HIDDEN) {
            return null;
        }
        PendingIntent pendingIntent = toPendingIntent(safetySource.getIntentAction(), packageName);
        // TODO(b/218817233): Add missing fields like: enabled.
        // TODO(b/222838784): Automatically mark the source as disabled (both dynamic and static?)
        //  if the pending intent is null.
        return new SafetyCenterEntry.Builder(
                        safetySource.getId(),
                        mSafetyCenterConfigReader.readStringResource(safetySource.getTitleResId()))
                .setSeverityLevel(entrySeverityLevel)
                .setSummary(
                        mSafetyCenterConfigReader.readStringResource(
                                safetySource.getSummaryResId()))
                .setPendingIntent(pendingIntent)
                .build();
    }
    private void addSafetyCenterStaticEntryGroup(
            @NonNull List safetyCenterStaticEntryGroups,
            @NonNull SafetySourcesGroup safetySourcesGroup) {
        List safetySources = safetySourcesGroup.getSafetySources();
        List staticEntries = new ArrayList<>(safetySources.size());
        for (int i = 0; i < safetySources.size(); i++) {
            SafetySource safetySource = safetySources.get(i);
            if (safetySource.getType() != SafetySource.SAFETY_SOURCE_TYPE_STATIC) {
                Log.w(TAG, "Non-static safety source found in rigid group");
                continue;
            }
            PendingIntent pendingIntent = toPendingIntent(safetySource.getIntentAction(), null);
            if (pendingIntent == null) {
                // TODO(b/222838784): Decide strategy for static entries when the intent is null.
                continue;
            }
            staticEntries.add(
                    new SafetyCenterStaticEntry.Builder(
                                    mSafetyCenterConfigReader.readStringResource(
                                            safetySource.getTitleResId()))
                            .setSummary(
                                    mSafetyCenterConfigReader.readStringResource(
                                            safetySource.getSummaryResId()))
                            .setPendingIntent(pendingIntent)
                            .build());
        }
        safetyCenterStaticEntryGroups.add(
                new SafetyCenterStaticEntryGroup(
                        mSafetyCenterConfigReader.readStringResource(
                                safetySourcesGroup.getTitleResId()),
                        staticEntries));
    }
    @Nullable
    private PendingIntent toPendingIntent(
            @Nullable String intentAction, @Nullable String packageName) {
        if (intentAction == null) {
            return null;
        }
        Context context;
        if (packageName == null) {
            context = mContext;
        } else {
            final long identity = Binder.clearCallingIdentity();
            try {
                context = mContext.createPackageContext(packageName, 0);
            } catch (NameNotFoundException e) {
                Log.w(TAG, String.format("Package name %s not found", packageName), e);
                return null;
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
        // TODO(b/222838784): Validate that the intent action is available.
        // TODO(b/219699223): Is it safe to create a PendingIntent as system server here?
        final long identity = Binder.clearCallingIdentity();
        try {
            // TODO(b/218816518): May need to create a unique requestCode per PendingIntent.
            return PendingIntent.getActivity(
                    context, 0, new Intent(intentAction), PendingIntent.FLAG_IMMUTABLE);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }
    @Nullable
    private static SafetySourceStatus getSafetySourceStatus(
            @Nullable SafetySourceData safetySourceData) {
        if (safetySourceData == null) {
            return null;
        }
        return safetySourceData.getStatus();
    }
    @SafetyCenterStatus.OverallSeverityLevel
    private static int entryToSafetyCenterStatusOverallLevel(
            @SafetyCenterEntry.EntrySeverityLevel int safetyCenterEntrySeverityLevel) {
        switch (safetyCenterEntrySeverityLevel) {
            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN:
                return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK:
                return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
                return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION;
            case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
                return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING;
        }
        throw new IllegalArgumentException(
                String.format(
                        "Unexpected SafetyCenterEntry.EntrySeverityLevel: %s",
                        safetyCenterEntrySeverityLevel));
    }
    @SafetyCenterEntry.EntrySeverityLevel
    private static int issueToSafetyCenterEntryLevel(
            @SafetyCenterIssue.IssueSeverityLevel int safetyCenterIssueSeverityLevel) {
        switch (safetyCenterIssueSeverityLevel) {
            case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK:
                return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK;
            case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION:
                return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION;
            case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING:
                return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING;
        }
        throw new IllegalArgumentException(
                String.format(
                        "Unexpected SafetyCenterIssue.IssueSeverityLevel: %s",
                        safetyCenterIssueSeverityLevel));
    }
    @SafetyCenterEntry.EntrySeverityLevel
    private static int sourceToSafetyCenterEntrySeverityLevel(
            @SafetySourceData.SeverityLevel int safetySourceSeverityLevel) {
        switch (safetySourceSeverityLevel) {
            case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
                return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
            case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
                return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK;
            case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
                return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION;
            case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
                return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING;
        }
        throw new IllegalArgumentException(
                String.format(
                        "Unexpected SafetySourceSeverity.Level in SafetySourceStatus: %s",
                        safetySourceSeverityLevel));
    }
    @SafetyCenterIssue.IssueSeverityLevel
    private static int sourceToSafetyCenterIssueSeverityLevel(
            @SafetySourceData.SeverityLevel int safetySourceIssueSeverityLevel) {
        switch (safetySourceIssueSeverityLevel) {
            case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
                Log.w(
                        TAG,
                        "Unexpected use of SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED in "
                                + "SafetySourceStatus");
                return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK;
            case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
                return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK;
            case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
                return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION;
            case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
                return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING;
        }
        throw new IllegalArgumentException(
                String.format(
                        "Unexpected SafetySourceSeverity.Level in SafetySourceIssue: %s",
                        safetySourceIssueSeverityLevel));
    }
    // TODO(b/218801295): Use the right strings and localize them.
    private static String getSafetyCenterStatusTitle(
            @SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel) {
        switch (overallSeverityLevel) {
            case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
                return "Unknown";
            case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
                return "All good";
            case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
                return "Some warnings";
            case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
                return "Uh-oh";
        }
        throw new IllegalArgumentException(
                String.format(
                        "Unexpected SafetyCenterStatus.OverallSeverityLevel: %s",
                        overallSeverityLevel));
    }
    // TODO(b/218801295): Use the right strings and localize them.
    private static String getSafetyCenterStatusSummary(
            @SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel) {
        switch (overallSeverityLevel) {
            case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
                return "Unknown safety status";
            case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
                return "No problemo maestro";
            case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
                return "Careful there";
            case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
                return "Code red";
        }
        throw new IllegalArgumentException(
                String.format(
                        "Unexpected SafetyCenterStatus.OverallSeverityLevel: %s",
                        overallSeverityLevel));
    }
    /**
     * A key for {@link SafetySourceData}; based on the {@code safetySourceId}, {@code packageName}
     * and {@code userId}.
     */
    // TODO(b/219697341): Look into using AutoValue for this data class.
    private static final class Key {
        @NonNull private final String mSafetySourceId;
        @NonNull private final String mPackageName;
        @UserIdInt private final int mUserId;
        private Key(
                @NonNull String safetySourceId,
                @NonNull String packageName,
                @UserIdInt int userId) {
            mSafetySourceId = safetySourceId;
            mPackageName = packageName;
            mUserId = userId;
        }
        @NonNull
        private static Key of(
                @NonNull String safetySourceId,
                @NonNull String packageName,
                @UserIdInt int userId) {
            return new Key(safetySourceId, packageName, userId);
        }
        @Override
        public String toString() {
            return "Key{"
                    + "mSafetySourceId='"
                    + mSafetySourceId
                    + '\''
                    + ", mPackageName='"
                    + mPackageName
                    + '\''
                    + ", mUserId="
                    + mUserId
                    + '\''
                    + '}';
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Key)) return false;
            Key key = (Key) o;
            return mSafetySourceId.equals(key.mSafetySourceId)
                    && mPackageName.equals(key.mPackageName)
                    && mUserId == key.mUserId;
        }
        @Override
        public int hashCode() {
            return Objects.hash(mSafetySourceId, mPackageName, mUserId);
        }
    }
}