diff options
| author | 2024-08-15 09:58:18 -0700 | |
|---|---|---|
| committer | 2024-08-28 23:38:56 +0000 | |
| commit | 13c600389236f6df56f90bf0bf689dd06707829e (patch) | |
| tree | 6facc29a1bbfb609b61455b48aa250ca23b652ff | |
| parent | 18fb3cee1a5aad5cb76c4d25bc62b3f27f1e13a9 (diff) | |
Allow gesture blocking by activity type.
This changelist adds another way activities can have gestures blocked by
activity type. This blocking is done globally for all top activities of
a given type. This changelist also utilizes this functionality for
dreams via the DreamOverlayService.
Test: atest DreamOverlayServiceTest#testDreamActivityGesturesBlockedWhenDreaming
Test: atest GestureInteractorTest
Test: atest GestureRepositoryTest
Test: atest TaskmatcherTest
Flag: EXEMPT bugfix
Fixes: 333734282
Change-Id: I882726aa01ea36fba25316a8e368251ae793bd86
9 files changed, 291 insertions, 96 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index 3f2faeb40eeb..a068118051f9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.dreams +import android.app.WindowConfiguration import android.content.ComponentName import android.content.Intent import android.os.RemoteException @@ -65,6 +66,8 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.gesture.domain.gestureInteractor import com.android.systemui.kosmos.testScope import com.android.systemui.navigationbar.gestural.domain.GestureInteractor +import com.android.systemui.navigationbar.gestural.domain.TaskInfo +import com.android.systemui.navigationbar.gestural.domain.TaskMatcher import com.android.systemui.testKosmos import com.android.systemui.touch.TouchInsetManager import com.android.systemui.util.concurrency.FakeExecutor @@ -83,6 +86,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.isNull +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.kotlin.any @@ -1044,7 +1048,7 @@ class DreamOverlayServiceTest : SysuiTestCase() { } @Test - fun testDreamActivityGesturesBlockedOnStart() { + fun testDreamActivityGesturesBlockedWhenDreaming() { val client = client // Inform the overlay service of dream starting. @@ -1056,15 +1060,24 @@ class DreamOverlayServiceTest : SysuiTestCase() { false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - val captor = argumentCaptor<ComponentName>() + + val matcherCaptor = argumentCaptor<TaskMatcher>() verify(gestureInteractor) - .addGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global)) - assertThat(captor.firstValue.packageName) - .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName) + .addGestureBlockedMatcher(matcherCaptor.capture(), eq(GestureInteractor.Scope.Global)) + val matcher = matcherCaptor.firstValue + + val dreamTaskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_DREAM) + assertThat(matcher.matches(dreamTaskInfo)).isTrue() + + client.endDream() + mMainExecutor.runAllReady() + + verify(gestureInteractor) + .removeGestureBlockedMatcher(eq(matcher), eq(GestureInteractor.Scope.Global)) } @Test - fun testDreamActivityGesturesUnblockedOnEnd() { + fun testDreamActivityGesturesNotBlockedWhenPreview() { val client = client // Inform the overlay service of dream starting. @@ -1072,18 +1085,13 @@ class DreamOverlayServiceTest : SysuiTestCase() { mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, - false /*isPreview*/, + true /*isPreview*/, false /*shouldShowComplication*/ ) mMainExecutor.runAllReady() - client.endDream() - mMainExecutor.runAllReady() - val captor = argumentCaptor<ComponentName>() - verify(gestureInteractor) - .removeGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global)) - assertThat(captor.firstValue.packageName) - .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName) + verify(gestureInteractor, never()) + .addGestureBlockedMatcher(any(), eq(GestureInteractor.Scope.Global)) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt index 91d37cf1816a..a764256df9a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt @@ -16,12 +16,14 @@ package com.android.systemui.gesture.data +import android.app.WindowConfiguration import android.content.ComponentName 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.navigationbar.gestural.data.respository.GestureRepositoryImpl +import com.android.systemui.navigationbar.gestural.domain.TaskMatcher import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -40,14 +42,36 @@ class GestureRepositoryTest : SysuiTestCase() { @Test fun addRemoveComponentToBlock_updatesBlockedComponentSet() = testScope.runTest { - val component = mock<ComponentName>() + val matcher = TaskMatcher.TopActivityComponent(mock<ComponentName>()) - underTest.addGestureBlockedActivity(component) - val addedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities) - assertThat(addedBlockedComponents).contains(component) + kotlin.run { + underTest.addGestureBlockedMatcher(matcher) + val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers) + assertThat(blockedMatchers).contains(matcher) + } - underTest.removeGestureBlockedActivity(component) - val removedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities) - assertThat(removedBlockedComponents).isEmpty() + kotlin.run { + underTest.removeGestureBlockedMatcher(matcher) + val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers) + assertThat(blockedMatchers).doesNotContain(matcher) + } + } + + @Test + fun addRemoveActivityTypeToBlock_updatesBlockedActivityTypesSet() = + testScope.runTest { + val matcher = TaskMatcher.TopActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD) + + kotlin.run { + underTest.addGestureBlockedMatcher(matcher) + val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers) + assertThat(blockedMatchers).contains(matcher) + } + + kotlin.run { + underTest.removeGestureBlockedMatcher(matcher) + val blockedMatchers by collectLastValue(underTest.gestureBlockedMatchers) + assertThat(blockedMatchers).doesNotContain(matcher) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt index 639544889c2a..0ce0d934c381 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.kosmos.testDispatcher import com.android.systemui.navigationbar.gestural.data.gestureRepository import com.android.systemui.navigationbar.gestural.domain.GestureInteractor +import com.android.systemui.navigationbar.gestural.domain.TaskMatcher import com.android.systemui.shared.system.activityManagerWrapper import com.android.systemui.shared.system.taskStackChangeListeners import com.android.systemui.testKosmos @@ -76,13 +77,16 @@ class GestureInteractorTest : SysuiTestCase() { fun addBlockedActivity_testCombination() = testScope.runTest { val globalComponent = mock<ComponentName>() - repository.addGestureBlockedActivity(globalComponent) + repository.addGestureBlockedMatcher(TaskMatcher.TopActivityComponent(globalComponent)) val localComponent = mock<ComponentName>() val blocked by collectLastValue(underTest.topActivityBlocked) - underTest.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local) + underTest.addGestureBlockedMatcher( + TaskMatcher.TopActivityComponent(localComponent), + GestureInteractor.Scope.Local + ) assertThat(blocked).isFalse() @@ -95,7 +99,7 @@ class GestureInteractorTest : SysuiTestCase() { fun initialization_testEmit() = testScope.runTest { val globalComponent = mock<ComponentName>() - repository.addGestureBlockedActivity(globalComponent) + repository.addGestureBlockedMatcher(TaskMatcher.TopActivityComponent(globalComponent)) setTopActivity(globalComponent) val interactor = createInteractor() @@ -114,10 +118,36 @@ class GestureInteractorTest : SysuiTestCase() { val localComponent = mock<ComponentName>() - interactor1.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local) + interactor1.addGestureBlockedMatcher( + TaskMatcher.TopActivityComponent(localComponent), + GestureInteractor.Scope.Local + ) setTopActivity(localComponent) assertThat(interactor1Blocked).isTrue() assertThat(interactor2Blocked).isFalse() } + + @Test + fun matchingBlockers_separatelyManaged() = + testScope.runTest { + val interactor = createInteractor() + val interactorBlocked by collectLastValue(interactor.topActivityBlocked) + + val localComponent = mock<ComponentName>() + + val matcher1 = TaskMatcher.TopActivityComponent(localComponent) + val matcher2 = TaskMatcher.TopActivityComponent(localComponent) + + interactor.addGestureBlockedMatcher(matcher1, GestureInteractor.Scope.Local) + interactor.addGestureBlockedMatcher(matcher2, GestureInteractor.Scope.Local) + setTopActivity(localComponent) + assertThat(interactorBlocked).isTrue() + + interactor.removeGestureBlockedMatcher(matcher1, GestureInteractor.Scope.Local) + assertThat(interactorBlocked).isTrue() + + interactor.removeGestureBlockedMatcher(matcher2, GestureInteractor.Scope.Local) + assertThat(interactorBlocked).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt new file mode 100644 index 000000000000..a246270cf413 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt @@ -0,0 +1,91 @@ +/* + * 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.gesture.domain + +import android.app.WindowConfiguration +import android.content.ComponentName +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.navigationbar.gestural.domain.TaskInfo +import com.android.systemui.navigationbar.gestural.domain.TaskMatcher +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +@SmallTest +class TaskMatcherTest : SysuiTestCase() { + @Test + fun activityMatcher_matchesComponentName() { + val componentName = ComponentName.unflattenFromString("com.foo/.bar")!! + val matcher = TaskMatcher.TopActivityComponent(componentName) + + val taskInfo = TaskInfo(componentName, WindowConfiguration.ACTIVITY_TYPE_STANDARD) + assertThat(matcher.matches(taskInfo)).isTrue() + } + + @Test + fun activityMatcher_doesNotMatchComponentName() { + val componentName = ComponentName.unflattenFromString("com.foo/.bar")!! + val matcher = TaskMatcher.TopActivityComponent(componentName) + + val taskInfo = + TaskInfo( + ComponentName.unflattenFromString("com.bar/.baz"), + WindowConfiguration.ACTIVITY_TYPE_STANDARD + ) + assertThat(matcher.matches(taskInfo)).isFalse() + } + + @Test + fun activityMatcher_matchesActivityType() { + val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME + val matcher = TaskMatcher.TopActivityType(activityType) + + val taskInfo = TaskInfo(mock<ComponentName>(), activityType) + assertThat(matcher.matches(taskInfo)).isTrue() + } + + @Test + fun activityMatcher_doesNotMatchEmptyActivityType() { + val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME + val matcher = TaskMatcher.TopActivityType(activityType) + + val taskInfo = TaskInfo(null, activityType) + assertThat(matcher.matches(taskInfo)).isFalse() + } + + @Test + fun activityMatcher_doesNotMatchActivityType() { + val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME + val matcher = TaskMatcher.TopActivityType(activityType) + + val taskInfo = TaskInfo(mock<ComponentName>(), WindowConfiguration.ACTIVITY_TYPE_STANDARD) + assertThat(matcher.matches(taskInfo)).isFalse() + } + + @Test + fun activityMatcher_equivalentMatchersAreNotEqual() { + val activityType = WindowConfiguration.ACTIVITY_TYPE_HOME + val matcher1 = TaskMatcher.TopActivityType(activityType) + val matcher2 = TaskMatcher.TopActivityType(activityType) + + assertThat(matcher1).isNotEqualTo(matcher2) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index caf5b01db846..c3bc24f480dc 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -24,12 +24,11 @@ import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_M import static com.android.systemui.dreams.dagger.DreamModule.HOME_CONTROL_PANEL_DREAM_COMPONENT; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.graphics.drawable.ColorDrawable; -import android.service.dreams.DreamActivity; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -65,6 +64,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.navigationbar.gestural.domain.GestureInteractor; +import com.android.systemui.navigationbar.gestural.domain.TaskMatcher; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.touch.TouchInsetManager; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -89,6 +89,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ LifecycleOwner { private static final String TAG = "DreamOverlayService"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final TaskMatcher DREAM_TYPE_MATCHER = + new TaskMatcher.TopActivityType(WindowConfiguration.ACTIVITY_TYPE_DREAM); // The Context is used to construct the hosting constraint layout and child overlay views. private final Context mContext; @@ -141,10 +143,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private final TouchInsetManager mTouchInsetManager; private final LifecycleOwner mLifecycleOwner; - - - private ComponentName mCurrentBlockedGestureDreamActivityComponent; - private final ArrayList<Job> mFlows = new ArrayList<>(); /** @@ -420,7 +418,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mStarted = true; updateRedirectWakeup(); - updateBlockedGestureDreamActivityComponent(); + + if (!isDreamInPreviewMode()) { + mGestureInteractor.addGestureBlockedMatcher(DREAM_TYPE_MATCHER, + GestureInteractor.Scope.Global); + } } private void updateRedirectWakeup() { @@ -431,18 +433,6 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming()); } - private void updateBlockedGestureDreamActivityComponent() { - // TODO(b/343815446): We should not be crafting this ActivityInfo ourselves. It should be - // in a common place, Such as DreamActivity itself. - final ActivityInfo info = new ActivityInfo(); - info.name = DreamActivity.class.getName(); - info.packageName = getDreamComponent().getPackageName(); - mCurrentBlockedGestureDreamActivityComponent = info.getComponentName(); - - mGestureInteractor.addGestureBlockedActivity(mCurrentBlockedGestureDreamActivityComponent, - GestureInteractor.Scope.Global); - } - @Override public void onEndDream() { resetCurrentDreamOverlayLocked(); @@ -613,11 +603,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mWindow = null; // Always unregister the any set DreamActivity from being blocked from gestures. - if (mCurrentBlockedGestureDreamActivityComponent != null) { - mGestureInteractor.removeGestureBlockedActivity( - mCurrentBlockedGestureDreamActivityComponent, GestureInteractor.Scope.Global); - mCurrentBlockedGestureDreamActivityComponent = null; - } + mGestureInteractor.removeGestureBlockedMatcher(DREAM_TYPE_MATCHER, + GestureInteractor.Scope.Global); mStarted = false; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 0f82e024afe6..6bd880d56bbb 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -78,6 +78,7 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.gestural.domain.GestureInteractor; +import com.android.systemui.navigationbar.gestural.domain.TaskMatcher; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.plugins.PluginListener; @@ -474,9 +475,14 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } else { String[] gestureBlockingActivities = resources.getStringArray(resId); for (String gestureBlockingActivity : gestureBlockingActivities) { - mGestureInteractor.addGestureBlockedActivity( - ComponentName.unflattenFromString(gestureBlockingActivity), - GestureInteractor.Scope.Local); + final ComponentName component = + ComponentName.unflattenFromString(gestureBlockingActivity); + + if (component != null) { + mGestureInteractor.addGestureBlockedMatcher( + new TaskMatcher.TopActivityComponent(component), + GestureInteractor.Scope.Local); + } } } } catch (NameNotFoundException e) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt index 8f35343626e8..c1f238a03be2 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt @@ -16,10 +16,9 @@ package com.android.systemui.navigationbar.gestural.data.respository -import android.content.ComponentName -import android.util.ArraySet import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.navigationbar.gestural.domain.TaskMatcher import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow @@ -28,36 +27,43 @@ import kotlinx.coroutines.withContext /** A repository for storing gesture related information */ interface GestureRepository { - /** A {@link StateFlow} tracking activities currently blocked from gestures. */ - val gestureBlockedActivities: StateFlow<Set<ComponentName>> + /** A {@link StateFlow} tracking matchers that can block gestures. */ + val gestureBlockedMatchers: StateFlow<Set<TaskMatcher>> - /** Adds an activity to be blocked from gestures. */ - suspend fun addGestureBlockedActivity(activity: ComponentName) + /** Adds a matcher to determine whether a gesture should be blocked. */ + suspend fun addGestureBlockedMatcher(matcher: TaskMatcher) - /** Removes an activity from being blocked from gestures. */ - suspend fun removeGestureBlockedActivity(activity: ComponentName) + /** Removes a matcher from blocking from gestures. */ + suspend fun removeGestureBlockedMatcher(matcher: TaskMatcher) } @SysUISingleton class GestureRepositoryImpl @Inject constructor(@Main private val mainDispatcher: CoroutineDispatcher) : GestureRepository { - private val _gestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(ArraySet()) + private val _gestureBlockedMatchers = MutableStateFlow<Set<TaskMatcher>>(emptySet()) - override val gestureBlockedActivities: StateFlow<Set<ComponentName>> - get() = _gestureBlockedActivities + override val gestureBlockedMatchers: StateFlow<Set<TaskMatcher>> + get() = _gestureBlockedMatchers - override suspend fun addGestureBlockedActivity(activity: ComponentName) = + override suspend fun addGestureBlockedMatcher(matcher: TaskMatcher) = withContext(mainDispatcher) { - _gestureBlockedActivities.emit( - _gestureBlockedActivities.value.toMutableSet().apply { add(activity) } - ) + val existingMatchers = _gestureBlockedMatchers.value + if (existingMatchers.contains(matcher)) { + return@withContext + } + + _gestureBlockedMatchers.value = existingMatchers.toMutableSet().apply { add(matcher) } } - override suspend fun removeGestureBlockedActivity(activity: ComponentName) = + override suspend fun removeGestureBlockedMatcher(matcher: TaskMatcher) = withContext(mainDispatcher) { - _gestureBlockedActivities.emit( - _gestureBlockedActivities.value.toMutableSet().apply { remove(activity) } - ) + val existingMatchers = _gestureBlockedMatchers.value + if (!existingMatchers.contains(matcher)) { + return@withContext + } + + _gestureBlockedMatchers.value = + existingMatchers.toMutableSet().apply { remove(matcher) } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt index 61828783cdd9..96386e520d5a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.navigationbar.gestural.domain -import android.content.ComponentName import com.android.app.tracing.coroutines.flow.flowOn import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -25,7 +24,6 @@ import com.android.systemui.navigationbar.gestural.data.respository.GestureRepos import com.android.systemui.shared.system.ActivityManagerWrapper import com.android.systemui.shared.system.TaskStackChangeListener import com.android.systemui.shared.system.TaskStackChangeListeners -import com.android.systemui.util.kotlin.combine import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject @@ -60,7 +58,7 @@ constructor( Global } - private val _localGestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(setOf()) + private val _localGestureBlockedMatchers = MutableStateFlow<Set<TaskMatcher>>(setOf()) private val _topActivity = conflatedCallbackFlow { @@ -79,53 +77,47 @@ constructor( .mapLatest { getTopActivity() } .distinctUntilChanged() - private suspend fun getTopActivity(): ComponentName? = + private suspend fun getTopActivity(): TaskInfo? = withContext(backgroundCoroutineContext) { - val runningTask = activityManagerWrapper.runningTask - runningTask?.topActivity + activityManagerWrapper.runningTask?.let { TaskInfo(it.topActivity, it.activityType) } } val topActivityBlocked = combine( _topActivity, - gestureRepository.gestureBlockedActivities, - _localGestureBlockedActivities.asStateFlow() - ) { activity, global, local -> - activity != null && (global + local).contains(activity) + gestureRepository.gestureBlockedMatchers, + _localGestureBlockedMatchers.asStateFlow() + ) { runningTask, global, local -> + runningTask != null && (global + local).any { it.matches(runningTask) } } - /** - * Adds an {@link Activity} to be blocked based on component when the topmost, focused {@link - * Activity}. - */ - fun addGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) { + /** Adds an [TaskMatcher] to decide whether gestures should be blocked. */ + fun addGestureBlockedMatcher(matcher: TaskMatcher, gestureScope: Scope) { scope.launch { when (gestureScope) { Scope.Local -> { - _localGestureBlockedActivities.emit( - _localGestureBlockedActivities.value.toMutableSet().apply { add(activity) } + _localGestureBlockedMatchers.emit( + _localGestureBlockedMatchers.value.toMutableSet().apply { add(matcher) } ) } Scope.Global -> { - gestureRepository.addGestureBlockedActivity(activity) + gestureRepository.addGestureBlockedMatcher(matcher) } } } } - /** Removes an {@link Activity} from being blocked from gestures. */ - fun removeGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) { + /** Removes a gesture from deciding whether gestures should be blocked */ + fun removeGestureBlockedMatcher(matcher: TaskMatcher, gestureScope: Scope) { scope.launch { when (gestureScope) { Scope.Local -> { - _localGestureBlockedActivities.emit( - _localGestureBlockedActivities.value.toMutableSet().apply { - remove(activity) - } + _localGestureBlockedMatchers.emit( + _localGestureBlockedMatchers.value.toMutableSet().apply { remove(matcher) } ) } Scope.Global -> { - gestureRepository.removeGestureBlockedActivity(activity) + gestureRepository.removeGestureBlockedMatcher(matcher) } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt new file mode 100644 index 000000000000..d62b2c001fca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt @@ -0,0 +1,51 @@ +/* + * 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.navigationbar.gestural.domain + +import android.content.ComponentName + +/** + * A simple data class for capturing details around a task. Implements equality to ensure changes + * can be identified between emitted values. + */ +data class TaskInfo(val topActivity: ComponentName?, val topActivityType: Int) { + override fun equals(other: Any?): Boolean { + return other is TaskInfo && + other.topActivityType == topActivityType && + other.topActivity == topActivity + } +} + +/** + * [TaskMatcher] provides a way to identify a task based on particular attributes, such as the top + * activity type or component name. + */ +sealed interface TaskMatcher { + fun matches(info: TaskInfo): Boolean + + class TopActivityType(private val type: Int) : TaskMatcher { + override fun matches(info: TaskInfo): Boolean { + return info.topActivity != null && info.topActivityType == type + } + } + + class TopActivityComponent(private val component: ComponentName) : TaskMatcher { + override fun matches(info: TaskInfo): Boolean { + return component == info.topActivity + } + } +} |