summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Bryce Lee <brycelee@google.com> 2024-08-15 09:58:18 -0700
committer Bryce Lee <brycelee@google.com> 2024-08-28 23:38:56 +0000
commit13c600389236f6df56f90bf0bf689dd06707829e (patch)
tree6facc29a1bbfb609b61455b48aa250ca23b652ff
parent18fb3cee1a5aad5cb76c4d25bc62b3f27f1e13a9 (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
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/TaskMatcherTest.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/TaskMatcher.kt51
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
+ }
+ }
+}