Added UI for displaying existing custom shortcuts

+ Added button for deleting custom shortcuts commands if they exist for
  a particular shortcut

Test: manual - ensure custom shortcuts UI corresponds with UX mocks
Test: ShortcutCustomizationViewModelTest
Flag: com.android.systemui.keyboard_shortcut_helper_shortcut_customizer
Bug: 373620793
Change-Id: Ifa76e379959fbf6fcf332d99a520acf156770a2b
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
index 5f8570c..bf7df7e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
@@ -20,7 +20,9 @@
     val label: String,
     val commands: List<ShortcutCommand>,
     val icon: ShortcutIcon? = null,
-)
+) {
+    val containsCustomShortcutCommands: Boolean = commands.any { it.isCustom }
+}
 
 class ShortcutBuilder(private val label: String) {
     val commands = mutableListOf<ShortcutCommand>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
rename to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
index e4ccc2c..203228b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.keyboard.shortcut.shared.model
 
-data class ShortcutInfo(
-    val label: String,
-    val categoryType: ShortcutCategoryType,
-    val subCategoryLabel: String,
-)
+sealed interface ShortcutCustomizationRequestInfo {
+    data class Add(
+        val label: String,
+        val categoryType: ShortcutCategoryType,
+        val subCategoryLabel: String,
+    ) : ShortcutCustomizationRequestInfo
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index 02e206e..e44bfe3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -24,7 +24,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
 import com.android.systemui.keyboard.shortcut.ui.composable.AssignNewShortcutDialog
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
@@ -59,8 +59,8 @@
         }
     }
 
-    fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) {
-        viewModel.onAddShortcutDialogRequested(shortcutBeingCustomized)
+    fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) {
+        viewModel.onShortcutCustomizationRequested(requestInfo)
     }
 
     private fun createAddShortcutDialog(): Dialog {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
index 10a201e..fa03883 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
@@ -84,7 +84,7 @@
                     onKeyboardSettingsClicked = { onKeyboardSettingsClicked(dialog) },
                     onSearchQueryChanged = { shortcutHelperViewModel.onSearchQueryChanged(it) },
                     onCustomizationRequested = {
-                        shortcutCustomizationDialogStarter.onAddShortcutDialogRequested(it)
+                        shortcutCustomizationDialogStarter.onShortcutCustomizationRequested(it)
                     },
                 )
                 dialog.setOnDismissListener { shortcutHelperViewModel.onViewClosed() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 13934ea..71734a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -43,6 +43,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
@@ -53,6 +54,7 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.OpenInNew
 import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.DeleteOutline
 import androidx.compose.material.icons.filled.ExpandMore
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.Tune
@@ -71,7 +73,6 @@
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -109,8 +110,8 @@
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.ui.model.IconSource
@@ -125,7 +126,7 @@
     modifier: Modifier = Modifier,
     shortcutsUiState: ShortcutsUiState,
     useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() },
-    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
+    onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
 ) {
     when (shortcutsUiState) {
         is ShortcutsUiState.Active -> {
@@ -138,6 +139,7 @@
                 onCustomizationRequested,
             )
         }
+
         else -> {
             // No-op for now.
         }
@@ -151,7 +153,7 @@
     onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier,
     onKeyboardSettingsClicked: () -> Unit,
-    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
+    onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
 ) {
     var selectedCategoryType by
         remember(shortcutsUiState.defaultSelectedCategory) {
@@ -367,14 +369,10 @@
     onCategorySelected: (ShortcutCategoryType?) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
     isShortcutCustomizerFlagEnabled: Boolean,
-    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
+    onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
 ) {
     val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
-    var isCustomizeModeEntered by remember { mutableStateOf(false) }
-    val isCustomizing by
-        remember(isCustomizeModeEntered, isShortcutCustomizerFlagEnabled) {
-            derivedStateOf { isCustomizeModeEntered && isCustomizeModeEntered }
-        }
+    var isCustomizing by remember { mutableStateOf(false) }
 
     Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
         Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
@@ -383,10 +381,10 @@
             }
             Spacer(modifier = Modifier.weight(1f))
             if (isShortcutCustomizerFlagEnabled) {
-                if (isCustomizeModeEntered) {
-                    DoneButton(onClick = { isCustomizeModeEntered = false })
+                if (isCustomizing) {
+                    DoneButton(onClick = { isCustomizing = false })
                 } else {
-                    CustomizeButton(onClick = { isCustomizeModeEntered = true })
+                    CustomizeButton(onClick = { isCustomizing = true })
                 }
             }
         }
@@ -441,7 +439,7 @@
     modifier: Modifier,
     category: ShortcutCategoryUi?,
     isCustomizing: Boolean,
-    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
+    onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
 ) {
     val listState = rememberLazyListState()
     LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
@@ -457,7 +455,7 @@
                 isCustomizing = isCustomizing,
                 onCustomizationRequested = { label, subCategoryLabel ->
                     onCustomizationRequested(
-                        ShortcutInfo(
+                        ShortcutCustomizationRequestInfo.Add(
                             label = label,
                             subCategoryLabel = subCategoryLabel,
                             categoryType = category.type,
@@ -565,7 +563,7 @@
             modifier = Modifier.weight(1f),
             shortcut = shortcut,
             isCustomizing = isCustomizing,
-            onAddShortcutClicked = { onCustomizationRequested(shortcut.label) },
+            onAddShortcutRequested = { onCustomizationRequested(shortcut.label) },
         )
     }
 }
@@ -594,42 +592,92 @@
     modifier: Modifier = Modifier,
     shortcut: ShortcutModel,
     isCustomizing: Boolean = false,
-    onAddShortcutClicked: () -> Unit = {},
+    onAddShortcutRequested: () -> Unit = {},
+    onDeleteShortcutRequested: () -> Unit = {},
 ) {
     FlowRow(
         modifier = modifier,
         verticalArrangement = Arrangement.spacedBy(8.dp),
+        itemVerticalAlignment = Alignment.CenterVertically,
         horizontalArrangement = Arrangement.End,
     ) {
         shortcut.commands.forEachIndexed { index, command ->
             if (index > 0) {
                 ShortcutOrSeparator(spacing = 16.dp)
             }
-            ShortcutCommand(command)
+            ShortcutCommandContainer(showBackground = command.isCustom) { ShortcutCommand(command) }
         }
         if (isCustomizing) {
             Spacer(modifier = Modifier.width(16.dp))
-            ShortcutHelperButton(
-                modifier =
-                    Modifier.border(
-                        width = 1.dp,
-                        color = MaterialTheme.colorScheme.outline,
-                        shape = CircleShape,
-                    ),
-                onClick = { onAddShortcutClicked() },
-                color = Color.Transparent,
-                width = 32.dp,
-                height = 32.dp,
-                iconSource = IconSource(imageVector = Icons.Default.Add),
-                contentColor = MaterialTheme.colorScheme.primary,
-                contentPaddingVertical = 0.dp,
-                contentPaddingHorizontal = 0.dp,
-            )
+            if (shortcut.containsCustomShortcutCommands) {
+                DeleteShortcutButton(onDeleteShortcutRequested)
+            } else {
+                AddShortcutButton(onAddShortcutRequested)
+            }
         }
     }
 }
 
 @Composable
+private fun AddShortcutButton(onClick: () -> Unit) {
+    ShortcutHelperButton(
+        modifier =
+            Modifier.border(
+                width = 1.dp,
+                color = MaterialTheme.colorScheme.outline,
+                shape = CircleShape,
+            ),
+        onClick = onClick,
+        color = Color.Transparent,
+        width = 32.dp,
+        height = 32.dp,
+        iconSource = IconSource(imageVector = Icons.Default.Add),
+        contentColor = MaterialTheme.colorScheme.primary,
+        contentPaddingVertical = 0.dp,
+        contentPaddingHorizontal = 0.dp,
+    )
+}
+
+@Composable
+private fun DeleteShortcutButton(onClick: () -> Unit) {
+    ShortcutHelperButton(
+        modifier =
+            Modifier.border(
+                width = 1.dp,
+                color = MaterialTheme.colorScheme.outline,
+                shape = CircleShape,
+            ),
+        onClick = onClick,
+        color = Color.Transparent,
+        width = 32.dp,
+        height = 32.dp,
+        iconSource = IconSource(imageVector = Icons.Default.DeleteOutline),
+        contentColor = MaterialTheme.colorScheme.primary,
+        contentPaddingVertical = 0.dp,
+        contentPaddingHorizontal = 0.dp,
+    )
+}
+
+@Composable
+private fun ShortcutCommandContainer(showBackground: Boolean, content: @Composable () -> Unit) {
+    if (showBackground) {
+        Box(
+            modifier =
+                Modifier.wrapContentSize()
+                    .background(
+                        color = MaterialTheme.colorScheme.outlineVariant,
+                        shape = RoundedCornerShape(16.dp),
+                    )
+                    .padding(4.dp)
+        ) {
+            content()
+        }
+    } else {
+        content()
+    }
+}
+
+@Composable
 private fun ShortcutCommand(command: ShortcutCommand) {
     Row {
         command.keys.forEachIndexed { keyIndex, key ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index b925387..e86da5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -19,7 +19,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.input.key.KeyEvent
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -30,32 +30,35 @@
 class ShortcutCustomizationViewModel
 @AssistedInject
 constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor) {
-    private val _shortcutBeingCustomized = mutableStateOf<ShortcutInfo?>(null)
+    private val _shortcutBeingCustomized = mutableStateOf<ShortcutCustomizationRequestInfo?>(null)
 
     private val _shortcutCustomizationUiState =
         MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive)
 
     val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow()
 
-    fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) {
-        _shortcutCustomizationUiState.value =
-            ShortcutCustomizationUiState.AddShortcutDialog(
-                shortcutLabel = shortcutBeingCustomized.label,
-                shouldShowErrorMessage = false,
-                isValidKeyCombination = false,
-                defaultCustomShortcutModifierKey =
-                    shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
-                isDialogShowing = false,
-            )
-
-        _shortcutBeingCustomized.value = shortcutBeingCustomized
+    fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) {
+        when (requestInfo) {
+            is ShortcutCustomizationRequestInfo.Add -> {
+                _shortcutCustomizationUiState.value =
+                    ShortcutCustomizationUiState.AddShortcutDialog(
+                        shortcutLabel = requestInfo.label,
+                        shouldShowErrorMessage = false,
+                        isValidKeyCombination = false,
+                        defaultCustomShortcutModifierKey =
+                            shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
+                        isDialogShowing = false,
+                    )
+                _shortcutBeingCustomized.value = requestInfo
+            }
+        }
     }
 
     fun onAddShortcutDialogShown() {
         _shortcutCustomizationUiState.update { uiState ->
-            (uiState as? ShortcutCustomizationUiState.AddShortcutDialog)
-                ?.let { it.copy(isDialogShowing = true) }
-                ?: uiState
+            (uiState as? ShortcutCustomizationUiState.AddShortcutDialog)?.copy(
+                isDialogShowing = true
+            ) ?: uiState
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
new file mode 100644
index 0000000..f8d8481
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyboard.shortcut.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutCustomizationViewModelTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val viewModel = kosmos.shortcutCustomizationViewModelFactory.create()
+
+    @Test
+    fun uiState_inactiveByDefault() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    @Test
+    fun uiState_correctlyUpdatedWhenAddShortcutCustomizationIsRequested() {
+        testScope.runTest {
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+            assertThat(uiState).isEqualTo(expectedStandardAddShortcutUiState)
+        }
+    }
+
+    @Test
+    fun uiState_consumedOnAddDialogShown() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            viewModel.onAddShortcutDialogShown()
+
+            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).isDialogShowing)
+                .isTrue()
+        }
+    }
+
+    @Test
+    fun uiState_inactiveAfterDialogIsDismissed() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            viewModel.onAddShortcutDialogShown()
+            viewModel.onAddShortcutDialogDismissed()
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    private val standardAddShortcutRequest =
+        ShortcutCustomizationRequestInfo.Add(
+            label = "Standard shortcut",
+            categoryType = ShortcutCategoryType.System,
+            subCategoryLabel = "Standard subcategory",
+        )
+
+    private val expectedStandardAddShortcutUiState =
+        ShortcutCustomizationUiState.AddShortcutDialog(
+            shortcutLabel = "Standard shortcut",
+            shouldShowErrorMessage = false,
+            isValidKeyCombination = false,
+            defaultCustomShortcutModifierKey =
+                ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+            isDialogShowing = false,
+        )
+}