Loading packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/keygesture/domain/KeyGestureDialogInteractorTest.kt 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * 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.systemui.accessibility.keygesture.domain import android.content.Intent import android.hardware.input.KeyGestureEvent import android.view.KeyEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.data.repository.AccessibilityShortcutsRepository import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock import org.mockito.kotlin.eq import org.mockito.kotlin.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class KeyGestureDialogInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val broadcastDispatcher = kosmos.broadcastDispatcher private val testDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope // mocks private val repository = mock(AccessibilityShortcutsRepository::class.java) private lateinit var underTest: KeyGestureDialogInteractor @Before fun setUp() { underTest = KeyGestureDialogInteractor(repository, broadcastDispatcher, testDispatcher) } @Test fun onPositiveButtonClick_enabledShortcutsForFakeTarget() { val enabledTargetName = "fakeTargetName" underTest.onPositiveButtonClick(enabledTargetName) verify(repository).enableShortcutsForTargets(eq(enabledTargetName)) } @Test fun keyGestureConfirmDialogRequest_invalidRequestReceived() { testScope.runTest { val keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION val metaState = 0 val keyCode = 0 val testTargetName = "fakeTargetName" val keyGestureConfirmInfo by collectLastValue(underTest.keyGestureConfirmDialogRequest) runCurrent() sendIntentBroadcast(keyGestureType, metaState, keyCode, testTargetName) runCurrent() assertThat(keyGestureConfirmInfo).isNull() } } @Test fun keyGestureConfirmDialogRequest_getFlowFromIntentForMagnification() { testScope.runTest { val keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION val metaState = KeyEvent.META_META_ON or KeyEvent.META_ALT_ON val keyCode = KeyEvent.KEYCODE_M val testTargetName = "targetNameForMagnification" collectLastValue(underTest.keyGestureConfirmDialogRequest) runCurrent() sendIntentBroadcast(keyGestureType, metaState, keyCode, testTargetName) runCurrent() verify(repository) .getKeyGestureConfirmInfoByType( eq(keyGestureType), eq(metaState), eq(keyCode), eq(testTargetName), ) } } private fun sendIntentBroadcast( keyGestureType: Int, metaState: Int, keyCode: Int, targetName: String, ) { val intent = Intent().apply { action = KeyGestureDialogInteractor.ACTION putExtra(KeyGestureDialogInteractor.EXTRA_KEY_GESTURE_TYPE, keyGestureType) putExtra(KeyGestureDialogInteractor.EXTRA_META_STATE, metaState) putExtra(KeyGestureDialogInteractor.EXTRA_KEY_CODE, keyCode) putExtra(KeyGestureDialogInteractor.EXTRA_TARGET_NAME, targetName) } broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent) } } packages/SystemUI/src/com/android/systemui/accessibility/keygesture/domain/KeyGestureDialogInteractor.kt 0 → 100644 +97 −0 Original line number Diff line number Diff line /* * 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.systemui.accessibility.keygesture.domain import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.UserHandle import androidx.annotation.VisibleForTesting import com.android.systemui.accessibility.data.model.KeyGestureConfirmInfo import com.android.systemui.accessibility.data.repository.AccessibilityShortcutsRepository import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext /** Encapsulates business logic to interact with the key gesture dialog. */ @SysUISingleton class KeyGestureDialogInteractor @Inject constructor( private val repository: AccessibilityShortcutsRepository, private val broadcastDispatcher: BroadcastDispatcher, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { /** Emits whenever a launch key gesture dialog broadcast is received. */ val keyGestureConfirmDialogRequest: Flow<KeyGestureConfirmInfo?> = broadcastDispatcher .broadcastFlow( filter = IntentFilter().apply { addAction(ACTION) }, user = UserHandle.SYSTEM, flags = Context.RECEIVER_NOT_EXPORTED, ) { intent, _ -> intent } .map { intent -> processDialogRequest(intent) } fun onPositiveButtonClick(targetName: String) { repository.enableShortcutsForTargets(targetName) } private suspend fun processDialogRequest(intent: Intent): KeyGestureConfirmInfo? { return withContext(backgroundDispatcher) { val keyGestureType = intent.getIntExtra(EXTRA_KEY_GESTURE_TYPE, 0) val targetName = intent.getStringExtra(EXTRA_TARGET_NAME) val metaState = intent.getIntExtra(EXTRA_META_STATE, 0) val keyCode = intent.getIntExtra(EXTRA_KEY_CODE, 0) if (isInvalidDialogRequest(keyGestureType, metaState, keyCode, targetName)) { null } else { repository.getKeyGestureConfirmInfoByType( keyGestureType, metaState, keyCode, targetName as String, ) } } } private fun isInvalidDialogRequest( keyGestureType: Int, metaState: Int, keyCode: Int, targetName: String?, ): Boolean { return targetName.isNullOrEmpty() || keyGestureType == 0 || metaState == 0 || keyCode == 0 } companion object { @VisibleForTesting const val ACTION = "com.android.systemui.action.LAUNCH_KEY_GESTURE_CONFIRM_DIALOG" @VisibleForTesting const val EXTRA_KEY_GESTURE_TYPE = "EXTRA_KEY_GESTURE_TYPE" @VisibleForTesting const val EXTRA_META_STATE = "EXTRA_META_STATE" @VisibleForTesting const val EXTRA_KEY_CODE = "EXTRA_KEY_CODE" @VisibleForTesting const val EXTRA_TARGET_NAME = "EXTRA_TARGET_NAME" } } packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/keygesture/domain/KeyGestureDialogInteractorKosmos.kt 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * 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.systemui.accessibility.keygesture.domain import com.android.systemui.accessibility.data.repository.accessibilityShortcutsRepository import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher val Kosmos.keyGestureDialogInteractor by Fixture { KeyGestureDialogInteractor( accessibilityShortcutsRepository, broadcastDispatcher, testDispatcher, ) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/keygesture/domain/KeyGestureDialogInteractorTest.kt 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * 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.systemui.accessibility.keygesture.domain import android.content.Intent import android.hardware.input.KeyGestureEvent import android.view.KeyEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.data.repository.AccessibilityShortcutsRepository import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock import org.mockito.kotlin.eq import org.mockito.kotlin.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class KeyGestureDialogInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val broadcastDispatcher = kosmos.broadcastDispatcher private val testDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope // mocks private val repository = mock(AccessibilityShortcutsRepository::class.java) private lateinit var underTest: KeyGestureDialogInteractor @Before fun setUp() { underTest = KeyGestureDialogInteractor(repository, broadcastDispatcher, testDispatcher) } @Test fun onPositiveButtonClick_enabledShortcutsForFakeTarget() { val enabledTargetName = "fakeTargetName" underTest.onPositiveButtonClick(enabledTargetName) verify(repository).enableShortcutsForTargets(eq(enabledTargetName)) } @Test fun keyGestureConfirmDialogRequest_invalidRequestReceived() { testScope.runTest { val keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION val metaState = 0 val keyCode = 0 val testTargetName = "fakeTargetName" val keyGestureConfirmInfo by collectLastValue(underTest.keyGestureConfirmDialogRequest) runCurrent() sendIntentBroadcast(keyGestureType, metaState, keyCode, testTargetName) runCurrent() assertThat(keyGestureConfirmInfo).isNull() } } @Test fun keyGestureConfirmDialogRequest_getFlowFromIntentForMagnification() { testScope.runTest { val keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION val metaState = KeyEvent.META_META_ON or KeyEvent.META_ALT_ON val keyCode = KeyEvent.KEYCODE_M val testTargetName = "targetNameForMagnification" collectLastValue(underTest.keyGestureConfirmDialogRequest) runCurrent() sendIntentBroadcast(keyGestureType, metaState, keyCode, testTargetName) runCurrent() verify(repository) .getKeyGestureConfirmInfoByType( eq(keyGestureType), eq(metaState), eq(keyCode), eq(testTargetName), ) } } private fun sendIntentBroadcast( keyGestureType: Int, metaState: Int, keyCode: Int, targetName: String, ) { val intent = Intent().apply { action = KeyGestureDialogInteractor.ACTION putExtra(KeyGestureDialogInteractor.EXTRA_KEY_GESTURE_TYPE, keyGestureType) putExtra(KeyGestureDialogInteractor.EXTRA_META_STATE, metaState) putExtra(KeyGestureDialogInteractor.EXTRA_KEY_CODE, keyCode) putExtra(KeyGestureDialogInteractor.EXTRA_TARGET_NAME, targetName) } broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent) } }
packages/SystemUI/src/com/android/systemui/accessibility/keygesture/domain/KeyGestureDialogInteractor.kt 0 → 100644 +97 −0 Original line number Diff line number Diff line /* * 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.systemui.accessibility.keygesture.domain import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.UserHandle import androidx.annotation.VisibleForTesting import com.android.systemui.accessibility.data.model.KeyGestureConfirmInfo import com.android.systemui.accessibility.data.repository.AccessibilityShortcutsRepository import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext /** Encapsulates business logic to interact with the key gesture dialog. */ @SysUISingleton class KeyGestureDialogInteractor @Inject constructor( private val repository: AccessibilityShortcutsRepository, private val broadcastDispatcher: BroadcastDispatcher, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { /** Emits whenever a launch key gesture dialog broadcast is received. */ val keyGestureConfirmDialogRequest: Flow<KeyGestureConfirmInfo?> = broadcastDispatcher .broadcastFlow( filter = IntentFilter().apply { addAction(ACTION) }, user = UserHandle.SYSTEM, flags = Context.RECEIVER_NOT_EXPORTED, ) { intent, _ -> intent } .map { intent -> processDialogRequest(intent) } fun onPositiveButtonClick(targetName: String) { repository.enableShortcutsForTargets(targetName) } private suspend fun processDialogRequest(intent: Intent): KeyGestureConfirmInfo? { return withContext(backgroundDispatcher) { val keyGestureType = intent.getIntExtra(EXTRA_KEY_GESTURE_TYPE, 0) val targetName = intent.getStringExtra(EXTRA_TARGET_NAME) val metaState = intent.getIntExtra(EXTRA_META_STATE, 0) val keyCode = intent.getIntExtra(EXTRA_KEY_CODE, 0) if (isInvalidDialogRequest(keyGestureType, metaState, keyCode, targetName)) { null } else { repository.getKeyGestureConfirmInfoByType( keyGestureType, metaState, keyCode, targetName as String, ) } } } private fun isInvalidDialogRequest( keyGestureType: Int, metaState: Int, keyCode: Int, targetName: String?, ): Boolean { return targetName.isNullOrEmpty() || keyGestureType == 0 || metaState == 0 || keyCode == 0 } companion object { @VisibleForTesting const val ACTION = "com.android.systemui.action.LAUNCH_KEY_GESTURE_CONFIRM_DIALOG" @VisibleForTesting const val EXTRA_KEY_GESTURE_TYPE = "EXTRA_KEY_GESTURE_TYPE" @VisibleForTesting const val EXTRA_META_STATE = "EXTRA_META_STATE" @VisibleForTesting const val EXTRA_KEY_CODE = "EXTRA_KEY_CODE" @VisibleForTesting const val EXTRA_TARGET_NAME = "EXTRA_TARGET_NAME" } }
packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/keygesture/domain/KeyGestureDialogInteractorKosmos.kt 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * 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.systemui.accessibility.keygesture.domain import com.android.systemui.accessibility.data.repository.accessibilityShortcutsRepository import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher val Kosmos.keyGestureDialogInteractor by Fixture { KeyGestureDialogInteractor( accessibilityShortcutsRepository, broadcastDispatcher, testDispatcher, ) }