diff options
5 files changed, 226 insertions, 91 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt index b5a66da06..d29b0aa3e 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt @@ -61,7 +61,6 @@ abstract class SafetyCenterFragment : SettingsBasePreferenceFragment() { } else { super.onCreateAdapter(preferenceScreen) } - /* By default, the PreferenceGroupAdapter does setHasStableIds(true). Since each Preference * is internally allocated with an auto-incremented ID, it does not allow us to gracefully * update only changed preferences based on SafetyPreferenceComparisonCallback. In order to @@ -77,10 +76,15 @@ abstract class SafetyCenterFragment : SettingsBasePreferenceFragment() { .split(",") safetyCenterSessionId = requireArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID) + val activity = requireActivity() safetyCenterViewModel = ViewModelProvider( - requireActivity(), - LiveSafetyCenterViewModelFactory(requireActivity().getApplication()), + activity, + LiveSafetyCenterViewModelFactory( + activity.application, + activity.taskId, + sameTaskSourceIds, + ), ) .get(SafetyCenterViewModel::class.java) safetyCenterViewModel.safetyCenterUiLiveData.observe(this) { uiData: SafetyCenterUiData? -> @@ -91,8 +95,7 @@ abstract class SafetyCenterFragment : SettingsBasePreferenceFragment() { displayErrorDetails(errorDetails) } - val safetyCenterIntent: ParsedSafetyCenterIntent = - requireActivity().intent.toSafetyCenterIntent() + val safetyCenterIntent: ParsedSafetyCenterIntent = activity.intent.toSafetyCenterIntent() val isQsFragment = getArguments()?.getBoolean(QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT, false) ?: false collapsableIssuesCardHelper = diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/IssueUiData.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/IssueUiData.kt new file mode 100644 index 000000000..e260bb917 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/IssueUiData.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2025 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.permissioncontroller.safetycenter.ui.model + +import android.safetycenter.SafetyCenterIssue + +/** UI model representation of [SafetyCenterIssue] */ +data class IssueUiData( + val issue: SafetyCenterIssue, + val isDismissed: Boolean, + val resolvedIssueActionId: String? = null, + val launchTaskId: Int? = null, +) diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt index 4ddcf1c3d..0b976f49d 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt @@ -43,11 +43,16 @@ import com.android.safetycenter.internaldata.SafetyCenterIds /* A SafetyCenterViewModel that talks to the real backing service for Safety Center. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) -class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { +class LiveSafetyCenterViewModel( + app: Application, + private val taskId: Int, + private val sameTaskSourceIds: List<String>, +) : SafetyCenterViewModel(app) { private val TAG: String = LiveSafetyCenterViewModel::class.java.simpleName override val statusUiLiveData: LiveData<StatusUiData> get() = safetyCenterUiLiveData.map { StatusUiData(it.safetyCenterData) } + override val safetyCenterUiLiveData: LiveData<SafetyCenterUiData> by this::_safetyCenterLiveData override val errorLiveData: LiveData<SafetyCenterErrorDetails> by this::_errorLiveData @@ -65,7 +70,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { private val safetyCenterManager = app.getSystemService(SafetyCenterManager::class.java)!! override fun getCurrentSafetyCenterDataAsUiData(): SafetyCenterUiData = - SafetyCenterUiData(safetyCenterManager.safetyCenterData) + uiData(safetyCenterManager.safetyCenterData) override fun dismissIssue(issue: SafetyCenterIssue) { safetyCenterManager.dismissSafetyCenterIssue(issue.id) @@ -74,7 +79,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { override fun executeIssueAction( issue: SafetyCenterIssue, action: SafetyCenterIssue.Action, - launchTaskId: Int? + launchTaskId: Int?, ) { val issueId = if (launchTaskId != null) { @@ -107,9 +112,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { override fun navigateToSafetyCenter(context: Context, navigationSource: NavigationSource?) { val intent = Intent(ACTION_SAFETY_CENTER) - if (navigationSource != null) { - navigationSource.addToIntent(intent) - } + navigationSource?.addToIntent(intent) context.startActivity(intent) } @@ -132,7 +135,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { } else { safetyCenterManager.refreshSafetySources( SafetyCenterManager.REFRESH_REASON_PAGE_OPEN, - safetySourceIds + safetySourceIds, ) } } @@ -174,7 +177,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { override fun onActive() { safetyCenterManager.addOnSafetyCenterDataChangedListener( getMainExecutor(app.applicationContext), - this + this, ) super.onActive() } @@ -209,7 +212,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { Log.d( TAG, "Received SafetyCenterData while issue resolution animations" + - " occurring. Will update UI with new data soon." + " occurring. Will update UI with new data soon.", ) return } @@ -254,7 +257,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { private fun isCurrentlyScanning(): Boolean = value?.safetyCenterData?.isScanning() ?: false private fun sendNextData() { - value = SafetyCenterUiData(safetyCenterDataQueue.removeFirst()) + value = uiData(safetyCenterDataQueue.removeFirst()) } private fun skipNextData() = safetyCenterDataQueue.removeFirst() @@ -270,7 +273,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { // The current SafetyCenterData still contains the resolved SafetyCenterIssue objects. // Send it with the resolved IDs so the UI can generate the correct preferences and // trigger the right animations for issue resolution. - value = SafetyCenterUiData(currentData, currentResolvedIssues) + value = uiData(currentData, currentResolvedIssues) } @MainThread @@ -279,6 +282,11 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) { maybeProcessDataToNextResolvedIssues() } } + + private fun uiData( + safetyCenterData: SafetyCenterData, + resolvedIssues: Map<IssueId, ActionId> = emptyMap(), + ) = SafetyCenterUiData(safetyCenterData, taskId, sameTaskSourceIds, resolvedIssues) } /** Returns inflight issues pending resolution */ @@ -309,8 +317,15 @@ private val SafetyCenterData.allResolvableIssues: Sequence<SafetyCenterIssue> } @RequiresApi(Build.VERSION_CODES.TIRAMISU) -class LiveSafetyCenterViewModelFactory(private val app: Application) : ViewModelProvider.Factory { +class LiveSafetyCenterViewModelFactory +@JvmOverloads +constructor( + private val app: Application, + private val taskId: Int = 0, + private val sameTaskSourceIds: List<String> = emptyList(), +) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { - @Suppress("UNCHECKED_CAST") return LiveSafetyCenterViewModel(app) as T + @Suppress("UNCHECKED_CAST") + return LiveSafetyCenterViewModel(app, taskId, sameTaskSourceIds) as T } } diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt index 69a315f08..d8aadae2f 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt @@ -29,11 +29,21 @@ import com.android.safetycenter.internaldata.SafetyCenterIds import com.android.safetycenter.internaldata.SafetyCenterIssueKey /** UI model representation of Safety Center Data */ +@RequiresApi(TIRAMISU) data class SafetyCenterUiData( val safetyCenterData: SafetyCenterData, - val resolvedIssues: Map<IssueId, ActionId> = emptyMap() + private val taskId: Int, + private val sameTaskSourceIds: List<String>, + val resolvedIssues: Map<IssueId, ActionId> = emptyMap(), ) { - @RequiresApi(TIRAMISU) + + val issueUiDatas: List<IssueUiData> by + lazy(LazyThreadSafetyMode.NONE) { + safetyCenterData.issues.map { + IssueUiData(it, false, resolvedIssues[it.id], getLaunchTaskIdForIssue(it)) + } + } + fun getMatchingIssue(issueKey: SafetyCenterIssueKey): SafetyCenterIssue? { return safetyCenterData.issues.find { SafetyCenterIds.issueIdFromString(it.id).safetyCenterIssueKey == issueKey @@ -67,7 +77,7 @@ data class SafetyCenterUiData( @RequiresApi(UPSIDE_DOWN_CAKE) private fun selectMatchingIssuesForGroup( groupId: String, - issues: List<SafetyCenterIssue> + issues: List<SafetyCenterIssue>, ): List<SafetyCenterIssue> { val issuesToGroups = safetyCenterData.extras.getBundle(ISSUES_TO_GROUPS_BUNDLE_KEY) return issues.filter { @@ -84,4 +94,12 @@ data class SafetyCenterUiData( @RequiresApi(UPSIDE_DOWN_CAKE) fun SafetyCenterData.visibleDismissedIssues() = dismissedIssues.filter { it.severityLevel > ISSUE_SEVERITY_LEVEL_OK } + + private fun getLaunchTaskIdForIssue(issue: SafetyCenterIssue): Int? { + val sourceId: String = + SafetyCenterIds.issueIdFromString(issue.id) + .getSafetyCenterIssueKey() + .getSafetySourceId() + return if (sameTaskSourceIds.contains(sourceId)) taskId else null + } } diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt index ca0392716..e53fabc90 100644 --- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt +++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt @@ -18,6 +18,7 @@ package com.android.permissioncontroller.tests.mocking.safetycenter.ui.model import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE import android.os.Bundle +import android.os.UserHandle import android.safetycenter.SafetyCenterData import android.safetycenter.SafetyCenterEntryGroup import android.safetycenter.SafetyCenterEntryOrGroup @@ -28,8 +29,14 @@ import android.safetycenter.SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATIO import android.safetycenter.SafetyCenterStatus import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress +import com.android.permissioncontroller.safetycenter.ui.model.ActionId +import com.android.permissioncontroller.safetycenter.ui.model.IssueId +import com.android.permissioncontroller.safetycenter.ui.model.IssueUiData import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData import com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY +import com.android.safetycenter.internaldata.SafetyCenterIds +import com.android.safetycenter.internaldata.SafetyCenterIssueId +import com.android.safetycenter.internaldata.SafetyCenterIssueKey import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -40,42 +47,42 @@ class SafetyCenterUiDataTest { @Test fun getMatchingGroup_validMatchingGroup_returnsExpectedEntryGroup() { - val matchingGroup = createSafetyCenterEntryGroup(MATCHING_GROUP_ID) - val nonMatchingGroup = createSafetyCenterEntryGroup(NON_MATCHING_GROUP_ID) + val matchingGroup = entryGroup(MATCHING_GROUP_ID) + val nonMatchingGroup = entryGroup(NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData(entryGroups = listOf(matchingGroup, nonMatchingGroup)) - val result = SafetyCenterUiData(safetyCenterData).getMatchingGroup(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingGroup(MATCHING_GROUP_ID) assertThat(result).isEqualTo(matchingGroup) } @Test fun getMatchingGroup_noMatchingGroup_returnsNull() { - val nonMatchingGroup = createSafetyCenterEntryGroup(NON_MATCHING_GROUP_ID) + val nonMatchingGroup = entryGroup(NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData(entryGroups = listOf(nonMatchingGroup)) - val result = SafetyCenterUiData(safetyCenterData).getMatchingGroup(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingGroup(MATCHING_GROUP_ID) assertThat(result).isNull() } @Test fun getMatchingIssues_defaultMatchingIssue_noExtras_returnsListOfIssues() { - val defaultMatchingIssue = createSafetyCenterIssue("id1", MATCHING_GROUP_ID) - val nonMatchingIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID) + val defaultMatchingIssue = issue("id1", MATCHING_GROUP_ID) + val nonMatchingIssue = issue("id2", NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData(issues = listOf(defaultMatchingIssue, nonMatchingIssue)) - val result = SafetyCenterUiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID) assertThat(result).containsExactly(defaultMatchingIssue) } @Test fun getMatchingIssues_defaultMatchingIssue_unrelatedExtras_returnsListOfIssues() { - val defaultMatchingIssue = createSafetyCenterIssue("id1", MATCHING_GROUP_ID) - val nonMatchingIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID) + val defaultMatchingIssue = issue("id1", MATCHING_GROUP_ID) + val nonMatchingIssue = issue("id2", NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData( issues = listOf(defaultMatchingIssue, nonMatchingIssue), @@ -84,21 +91,21 @@ class SafetyCenterUiDataTest { Bundle().apply { putStringArrayList( nonMatchingIssue.id, - arrayListOf(NON_MATCHING_GROUP_ID) + arrayListOf(NON_MATCHING_GROUP_ID), ) } - ) + ), ) - val result = SafetyCenterUiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID) assertThat(result).containsExactly(defaultMatchingIssue) } @Test fun getMatchingIssues_mappingMatchingIssue_returnsListOfIssues() { - val mappingMatchingIssue = createSafetyCenterIssue("id1", NON_MATCHING_GROUP_ID) - val nonMatchingIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID) + val mappingMatchingIssue = issue("id1", NON_MATCHING_GROUP_ID) + val nonMatchingIssue = issue("id2", NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData( issues = listOf(mappingMatchingIssue, nonMatchingIssue), @@ -107,51 +114,50 @@ class SafetyCenterUiDataTest { Bundle().apply { putStringArrayList( mappingMatchingIssue.id, - arrayListOf(MATCHING_GROUP_ID) + arrayListOf(MATCHING_GROUP_ID), ) } - ) + ), ) - val result = SafetyCenterUiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID) assertThat(result).containsExactly(mappingMatchingIssue) } @Test fun getMatchingIssues_noDefaultMatchingIssue_returnsEmptyList() { - val nonMatchingIssue = createSafetyCenterIssue("id1", NON_MATCHING_GROUP_ID) - val dismissedIssue = createSafetyCenterIssue("id2", MATCHING_GROUP_ID) + val nonMatchingIssue = issue("id1", NON_MATCHING_GROUP_ID) + val dismissedIssue = issue("id2", MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData( issues = listOf(nonMatchingIssue), - dismissedIssues = listOf(dismissedIssue) + dismissedIssues = listOf(dismissedIssue), ) - val result = SafetyCenterUiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID) assertThat(result).isEmpty() } @Test fun getMatchingDismissedIssues_defaultMatchingDismissedIssue_returnsListOfDismissedIssues() { - val defaultMatchingDismissedIssue = createSafetyCenterIssue("id1", MATCHING_GROUP_ID) - val nonMatchingDismissedIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID) + val defaultMatchingDismissedIssue = issue("id1", MATCHING_GROUP_ID) + val nonMatchingDismissedIssue = issue("id2", NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData( dismissedIssues = listOf(defaultMatchingDismissedIssue, nonMatchingDismissedIssue) ) - val result = - SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) assertThat(result).containsExactly(defaultMatchingDismissedIssue) } @Test fun getMatchingDismissedIssues_defaultMatchingDismissedIssue2_returnsListOfDismissedIssues() { - val defaultMatchingDismissedIssue = createSafetyCenterIssue("id1", MATCHING_GROUP_ID) - val nonMatchingDismissedIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID) + val defaultMatchingDismissedIssue = issue("id1", MATCHING_GROUP_ID) + val nonMatchingDismissedIssue = issue("id2", NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData( dismissedIssues = listOf(defaultMatchingDismissedIssue, nonMatchingDismissedIssue), @@ -160,22 +166,21 @@ class SafetyCenterUiDataTest { Bundle().apply { putStringArrayList( nonMatchingDismissedIssue.id, - arrayListOf(NON_MATCHING_GROUP_ID) + arrayListOf(NON_MATCHING_GROUP_ID), ) } - ) + ), ) - val result = - SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) assertThat(result).containsExactly(defaultMatchingDismissedIssue) } @Test fun getMatchingDismissedIssues_mappingMatchingDismissedIssue_returnsListOfDismissedIssues() { - val mappingMatchingDismissedIssue = createSafetyCenterIssue("id1", NON_MATCHING_GROUP_ID) - val nonMatchingDismissedIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID) + val mappingMatchingDismissedIssue = issue("id1", NON_MATCHING_GROUP_ID) + val nonMatchingDismissedIssue = issue("id2", NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData( dismissedIssues = listOf(mappingMatchingDismissedIssue, nonMatchingDismissedIssue), @@ -184,30 +189,28 @@ class SafetyCenterUiDataTest { Bundle().apply { putStringArrayList( mappingMatchingDismissedIssue.id, - arrayListOf(MATCHING_GROUP_ID) + arrayListOf(MATCHING_GROUP_ID), ) } - ) + ), ) - val result = - SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) assertThat(result).containsExactly(mappingMatchingDismissedIssue) } @Test fun getMatchingDismissedIssues_noDefaultMatchingDismissedIssue_returnsEmptyList() { - val nonMatchingDismissedIssue = createSafetyCenterIssue("id1", NON_MATCHING_GROUP_ID) - val nonDismissedIssue = createSafetyCenterIssue("id2", MATCHING_GROUP_ID) + val nonMatchingDismissedIssue = issue("id1", NON_MATCHING_GROUP_ID) + val nonDismissedIssue = issue("id2", MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData( issues = listOf(nonDismissedIssue), - dismissedIssues = listOf(nonMatchingDismissedIssue) + dismissedIssues = listOf(nonMatchingDismissedIssue), ) - val result = - SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) assertThat(result).isEmpty() } @@ -215,24 +218,12 @@ class SafetyCenterUiDataTest { @Test fun getMatchingDismissedIssues_doesntReturnGreenIssues() { val greenDismissedIssue = - createSafetyCenterIssue( - "id1", - MATCHING_GROUP_ID, - severityLevel = ISSUE_SEVERITY_LEVEL_OK - ) + issue("id1", MATCHING_GROUP_ID, severityLevel = ISSUE_SEVERITY_LEVEL_OK) val yellowDismissedIssue = - createSafetyCenterIssue( - "id2", - MATCHING_GROUP_ID, - severityLevel = ISSUE_SEVERITY_LEVEL_RECOMMENDATION - ) + issue("id2", MATCHING_GROUP_ID, severityLevel = ISSUE_SEVERITY_LEVEL_RECOMMENDATION) val redDismissedIssue = - createSafetyCenterIssue( - "id3", - MATCHING_GROUP_ID, - severityLevel = ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING - ) - val nonMatchingDismissedIssue = createSafetyCenterIssue("id4", NON_MATCHING_GROUP_ID) + issue("id3", MATCHING_GROUP_ID, severityLevel = ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING) + val nonMatchingDismissedIssue = issue("id4", NON_MATCHING_GROUP_ID) val safetyCenterData = createSafetyCenterData( dismissedIssues = @@ -240,25 +231,82 @@ class SafetyCenterUiDataTest { redDismissedIssue, yellowDismissedIssue, greenDismissedIssue, - nonMatchingDismissedIssue - ), + nonMatchingDismissedIssue, + ) ) - val result = - SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) + val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID) assertThat(result).containsExactly(redDismissedIssue, yellowDismissedIssue).inOrder() } + @Test + fun issueUiDatas_returnsIssueUiData() { + val issue1 = issue("id1", "group1") + val issue2 = issue("id2", "group2") + val safetyCenterData = createSafetyCenterData(listOf(issue1, issue2)) + + val result = uiData(safetyCenterData).issueUiDatas + + assertThat(result) + .containsExactly( + IssueUiData(issue1, isDismissed = false), + IssueUiData(issue2, isDismissed = false), + ) + .inOrder() + } + + @Test + fun issueUiDatas_withResolvedIssues_returnsExpectedIssueUiData() { + val resolvedActionId = "actionId" + val resolvedIssue = issue("resolvedId", "group1") + val unresolvedIssue = issue("unresolvedId", "group2") + val safetyCenterData = createSafetyCenterData(listOf(resolvedIssue, unresolvedIssue)) + + val result = + uiData(safetyCenterData, resolvedIssues = mapOf(resolvedIssue.id to resolvedActionId)) + .issueUiDatas + + assertThat(result[0].resolvedIssueActionId).isEqualTo(resolvedActionId) + assertThat(result[1].resolvedIssueActionId).isNull() + } + + @Test + fun issueUiDatas_withSameTaskSourceId_returnsExpectedIssueUiData() { + val taskId = 42 + val sameTaskSourceId = "sameTaskSourceId" + val sameTaskIssue = + issueWithEncodedId( + encodeIssueId("sameTaskIssue", sourceId = sameTaskSourceId), + "group1", + ) + val differentTaskIssue = issue("differentTaskIssue", "group2") + val safetyCenterData = createSafetyCenterData(listOf(sameTaskIssue, differentTaskIssue)) + + val result = + uiData(safetyCenterData, taskId, sameTaskSourceIds = listOf(sameTaskSourceId)) + .issueUiDatas + + assertThat(result[0].launchTaskId).isEqualTo(taskId) + assertThat(result[1].launchTaskId).isNull() + } + private companion object { const val MATCHING_GROUP_ID = "matching_group_id" const val NON_MATCHING_GROUP_ID = "non_matching_group_id" + private fun uiData( + safetyCenterData: SafetyCenterData, + taskId: Int = 0, + sameTaskSourceIds: List<String> = emptyList(), + resolvedIssues: Map<IssueId, ActionId> = emptyMap(), + ) = SafetyCenterUiData(safetyCenterData, taskId, sameTaskSourceIds, resolvedIssues) + fun createSafetyCenterData( issues: List<SafetyCenterIssue> = listOf(), entryGroups: List<SafetyCenterEntryGroup> = listOf(), dismissedIssues: List<SafetyCenterIssue> = listOf(), - extras: Bundle = Bundle() + extras: Bundle = Bundle(), ): SafetyCenterData { val safetyCenterStatus = SafetyCenterStatus.Builder("status title", "status summary").build() @@ -276,20 +324,44 @@ class SafetyCenterUiDataTest { return builder.build() } - fun createSafetyCenterEntryGroup(groupId: String) = + fun createSafetyCenterExtras(issuesToGroupsMapping: Bundle) = + Bundle().apply { putBundle(ISSUES_TO_GROUPS_BUNDLE_KEY, issuesToGroupsMapping) } + + fun entryGroup(groupId: String) = SafetyCenterEntryGroup.Builder(groupId, "group title").build() - fun createSafetyCenterIssue( + fun issue( issueId: String, groupId: String, - severityLevel: Int = ISSUE_SEVERITY_LEVEL_RECOMMENDATION + severityLevel: Int = ISSUE_SEVERITY_LEVEL_RECOMMENDATION, + ): SafetyCenterIssue = issueWithEncodedId(encodeIssueId(issueId), groupId, severityLevel) + + private fun issueWithEncodedId( + encodedIssueId: String, + groupId: String, + severityLevel: Int = ISSUE_SEVERITY_LEVEL_RECOMMENDATION, ) = - SafetyCenterIssue.Builder(issueId, "issue title", "issue summary") + SafetyCenterIssue.Builder(encodedIssueId, "issue title", "issue summary") .setSeverityLevel(severityLevel) .setGroupId(groupId) .build() - fun createSafetyCenterExtras(issuesToGroupsMapping: Bundle) = - Bundle().apply { putBundle(ISSUES_TO_GROUPS_BUNDLE_KEY, issuesToGroupsMapping) } + fun encodeIssueId( + sourceIssueId: String, + sourceId: String = "defaultSource", + issueTypeId: String = "defaultIssueTypeId", + ): String = + SafetyCenterIds.encodeToString( + SafetyCenterIssueId.newBuilder() + .setSafetyCenterIssueKey( + SafetyCenterIssueKey.newBuilder() + .setSafetySourceId(sourceId) + .setSafetySourceIssueId(sourceIssueId) + .setUserId(UserHandle.myUserId()) + .build() + ) + .setIssueTypeId(issueTypeId) + .build() + ) } } |