Disable edge back gesture handling for dreams when overlay is active.
This changelist adds new functionality for disabling gesture handling on
specific activity components at any given time. DreamOverlayService
uses this new functionality to disable gestures on DreamActivities when
they start.
Fixes: 333734282
Test: atest DreamOverlayServiceTest#testDreamActivityGesturesBlockedOnStart
Test: atest DreamOverlayServiceTest#testDreamActivityGesturesUnblockedOnEnd
Test: atest GestureInteractorTest
Test: atest GestureRepositoryTest
Flag: EXEMPT bugfix
Change-Id: If67a2ba9e069455a9c21e8d934872459e7d1b9f1
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 4c24ce2..5c09777 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -62,12 +62,13 @@
import com.android.systemui.dreams.complication.HideComplicationTouchHandler
import com.android.systemui.dreams.dagger.DreamOverlayComponent
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.testKosmos
import com.android.systemui.touch.TouchInsetManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -84,9 +85,11 @@
import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
-import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -166,6 +169,7 @@
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
private lateinit var communalRepository: FakeCommunalSceneRepository
private var viewCaptureSpy = spy(ViewCaptureFactory.getInstance(context))
+ private lateinit var gestureInteractor: GestureInteractor
@Captor var mViewCaptor: ArgumentCaptor<View>? = null
private lateinit var mService: DreamOverlayService
@@ -177,6 +181,7 @@
lifecycleRegistry = FakeLifecycleRegistry(mLifecycleOwner)
bouncerRepository = kosmos.fakeKeyguardBouncerRepository
communalRepository = kosmos.fakeCommunalSceneRepository
+ gestureInteractor = spy(kosmos.gestureInteractor)
whenever(mDreamOverlayComponent.getDreamOverlayContainerViewController())
.thenReturn(mDreamOverlayContainerViewController)
@@ -231,6 +236,7 @@
HOME_CONTROL_PANEL_DREAM_COMPONENT,
mDreamOverlayCallbackController,
kosmos.keyguardInteractor,
+ gestureInteractor,
WINDOW_NAME
)
}
@@ -955,6 +961,47 @@
assertThat(lifecycleRegistry.currentState).isEqualTo(Lifecycle.State.RESUMED)
}
+ @Test
+ fun testDreamActivityGesturesBlockedOnStart() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ false /*shouldShowComplication*/
+ )
+ mMainExecutor.runAllReady()
+ val captor = argumentCaptor<ComponentName>()
+ verify(gestureInteractor)
+ .addGestureBlockedActivity(captor.capture(), eq(GestureInteractor.Scope.Global))
+ assertThat(captor.firstValue.packageName)
+ .isEqualTo(ComponentName.unflattenFromString(DREAM_COMPONENT)?.packageName)
+ }
+
+ @Test
+ fun testDreamActivityGesturesUnblockedOnEnd() {
+ val client = client
+
+ // Inform the overlay service of dream starting.
+ client.startDream(
+ mWindowParams,
+ mDreamOverlayCallback,
+ DREAM_COMPONENT,
+ 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)
+ }
+
internal class FakeLifecycleRegistry(provider: LifecycleOwner) : LifecycleRegistry(provider) {
val mLifecycles: MutableList<State> = ArrayList()
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
new file mode 100644
index 0000000..91d37cf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/data/GestureRepositoryTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.data
+
+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.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GestureRepositoryTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val underTest by lazy { GestureRepositoryImpl(testDispatcher) }
+
+ @Test
+ fun addRemoveComponentToBlock_updatesBlockedComponentSet() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+
+ underTest.addGestureBlockedActivity(component)
+ val addedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities)
+ assertThat(addedBlockedComponents).contains(component)
+
+ underTest.removeGestureBlockedActivity(component)
+ val removedBlockedComponents by collectLastValue(underTest.gestureBlockedActivities)
+ assertThat(removedBlockedComponents).isEmpty()
+ }
+}
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
new file mode 100644
index 0000000..bc142e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/gesture/domain/GestureInteractorTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.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.GestureRepository
+import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GestureInteractorTest : SysuiTestCase() {
+ @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ val dispatcher = StandardTestDispatcher()
+ val testScope = TestScope(dispatcher)
+
+ @Mock private lateinit var gestureRepository: GestureRepository
+
+ private val underTest by lazy {
+ GestureInteractor(gestureRepository, testScope.backgroundScope)
+ }
+
+ @Before
+ fun setup() {
+ Dispatchers.setMain(dispatcher)
+ whenever(gestureRepository.gestureBlockedActivities).thenReturn(MutableStateFlow(setOf()))
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun addBlockedActivity_testCombination() =
+ testScope.runTest {
+ val globalComponent = mock<ComponentName>()
+ whenever(gestureRepository.gestureBlockedActivities)
+ .thenReturn(MutableStateFlow(setOf(globalComponent)))
+ val localComponent = mock<ComponentName>()
+ underTest.addGestureBlockedActivity(localComponent, GestureInteractor.Scope.Local)
+ val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
+ testScope.runCurrent()
+ verify(gestureRepository, never()).addGestureBlockedActivity(any())
+ assertThat(lastSeen).hasSize(2)
+ assertThat(lastSeen).containsExactly(globalComponent, localComponent)
+ }
+
+ @Test
+ fun addBlockedActivityLocally_onlyAffectsLocalInteractor() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+ underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Local)
+ val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
+ testScope.runCurrent()
+ verify(gestureRepository, never()).addGestureBlockedActivity(any())
+ assertThat(lastSeen).contains(component)
+ }
+
+ @Test
+ fun removeBlockedActivityLocally_onlyAffectsLocalInteractor() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+ underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Local)
+ val lastSeen by collectLastValue(underTest.gestureBlockedActivities)
+ testScope.runCurrent()
+ underTest.removeGestureBlockedActivity(component, GestureInteractor.Scope.Local)
+ testScope.runCurrent()
+ verify(gestureRepository, never()).removeGestureBlockedActivity(any())
+ assertThat(lastSeen).isEmpty()
+ }
+
+ @Test
+ fun addBlockedActivity_invokesRepository() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+ underTest.addGestureBlockedActivity(component, GestureInteractor.Scope.Global)
+ runCurrent()
+ val captor = argumentCaptor<ComponentName>()
+ verify(gestureRepository).addGestureBlockedActivity(captor.capture())
+ assertThat(captor.firstValue).isEqualTo(component)
+ }
+
+ @Test
+ fun removeBlockedActivity_invokesRepository() =
+ testScope.runTest {
+ val component = mock<ComponentName>()
+ underTest.removeGestureBlockedActivity(component, GestureInteractor.Scope.Global)
+ runCurrent()
+ val captor = argumentCaptor<ComponentName>()
+ verify(gestureRepository).removeGestureBlockedActivity(captor.capture())
+ assertThat(captor.firstValue).isEqualTo(component)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1771f4d..15ddf5b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -80,6 +80,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
+import com.android.systemui.navigationbar.gestural.dagger.GestureModule;
import com.android.systemui.notetask.NoteTaskModule;
import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceConfigPlugin;
@@ -215,6 +216,7 @@
FlagsModule.class,
FlagDependenciesModule.class,
FooterActionsModule.class,
+ GestureModule.class,
InputMethodModule.class,
KeyEventRepositoryModule.class,
KeyboardModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 9823985..931066d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -27,7 +27,9 @@
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;
@@ -63,6 +65,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.shade.ShadeExpansionChangeEvent;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -135,6 +138,8 @@
private final DreamOverlayComponent mDreamOverlayComponent;
+ private ComponentName mCurrentBlockedGestureDreamActivityComponent;
+
/**
* This {@link LifecycleRegistry} controls when dream overlay functionality, like touch
* handling, should be active. It will automatically be paused when the dream overlay is hidden
@@ -222,6 +227,8 @@
private final DreamOverlayStateController mStateController;
+ private final GestureInteractor mGestureInteractor;
+
@VisibleForTesting
public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "The dream overlay has entered start.")
@@ -265,6 +272,7 @@
ComponentName homeControlPanelDreamComponent,
DreamOverlayCallbackController dreamOverlayCallbackController,
KeyguardInteractor keyguardInteractor,
+ GestureInteractor gestureInteractor,
@Named(DREAM_OVERLAY_WINDOW_TITLE) String windowTitle) {
super(executor);
mContext = context;
@@ -281,6 +289,7 @@
mWindowTitle = windowTitle;
mCommunalInteractor = communalInteractor;
mSystemDialogsCloser = systemDialogsCloser;
+ mGestureInteractor = gestureInteractor;
final ViewModelStore viewModelStore = new ViewModelStore();
final Complication.Host host =
@@ -391,6 +400,7 @@
mStarted = true;
updateRedirectWakeup();
+ updateBlockedGestureDreamActivityComponent();
}
private void updateRedirectWakeup() {
@@ -401,6 +411,18 @@
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();
@@ -472,6 +494,7 @@
* into the dream window.
*/
private boolean addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
+
mWindow = new PhoneWindow(mContext);
// Default to SystemUI name for TalkBack.
mWindow.setTitle(mWindowTitle);
@@ -554,6 +577,14 @@
}
mWindow = null;
+
+ // Always unregister the any set DreamActivity from being blocked from gestures.
+ if (mCurrentBlockedGestureDreamActivityComponent != null) {
+ mGestureInteractor.removeGestureBlockedActivity(
+ mCurrentBlockedGestureDreamActivityComponent, GestureInteractor.Scope.Global);
+ mCurrentBlockedGestureDreamActivityComponent = null;
+ }
+
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 947336d..9eca34f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -76,6 +76,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.plugins.FalsingManager;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
@@ -95,15 +96,14 @@
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.util.concurrency.BackPanelUiThread;
import com.android.systemui.util.concurrency.UiThreadContext;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.ArrayDeque;
-import java.util.ArrayList;
import java.util.Date;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -157,12 +157,7 @@
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
- if (edgebackGestureHandlerGetRunningTasksBackground()) {
- mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
- isGestureBlockingActivityRunning()));
- } else {
- mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
- }
+ updateRunningActivityGesturesBlocked();
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -209,8 +204,6 @@
private final Optional<DesktopMode> mDesktopModeOptional;
private final FalsingManager mFalsingManager;
private final Configuration mLastReportedConfig = new Configuration();
- // Activities which should not trigger Back gesture.
- private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>();
private final Point mDisplaySize = new Point();
private final int mDisplayId;
@@ -227,6 +220,10 @@
mBackGestureTfClassifierProviderProvider;
private final Provider<LightBarController> mLightBarControllerProvider;
+ private final GestureInteractor mGestureInteractor;
+
+ private final JavaAdapter mJavaAdapter;
+
// The left side edge width where touch down is allowed
private int mEdgeWidthLeft;
// The right side edge width where touch down is allowed
@@ -426,7 +423,9 @@
FalsingManager falsingManager,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
Provider<LightBarController> lightBarControllerProvider,
- NotificationShadeWindowController notificationShadeWindowController) {
+ NotificationShadeWindowController notificationShadeWindowController,
+ GestureInteractor gestureInteractor,
+ JavaAdapter javaAdapter) {
mContext = context;
mDisplayId = context.getDisplayId();
mUiThreadContext = uiThreadContext;
@@ -446,7 +445,13 @@
mFalsingManager = falsingManager;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
mLightBarControllerProvider = lightBarControllerProvider;
+ mGestureInteractor = gestureInteractor;
+ mJavaAdapter = javaAdapter;
mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
+
+ mJavaAdapter.alwaysCollectFlow(mGestureInteractor.getGestureBlockedActivities(),
+ componentNames -> updateRunningActivityGesturesBlocked());
+
ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName != null) {
@@ -466,8 +471,9 @@
} else {
String[] gestureBlockingActivities = resources.getStringArray(resId);
for (String gestureBlockingActivity : gestureBlockingActivities) {
- mGestureBlockingActivities.add(
- ComponentName.unflattenFromString(gestureBlockingActivity));
+ mGestureInteractor.addGestureBlockedActivity(
+ ComponentName.unflattenFromString(gestureBlockingActivity),
+ GestureInteractor.Scope.Local);
}
}
} catch (NameNotFoundException e) {
@@ -561,6 +567,15 @@
}
}
+ private void updateRunningActivityGesturesBlocked() {
+ if (edgebackGestureHandlerGetRunningTasksBackground()) {
+ mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
+ isGestureBlockingActivityRunning()));
+ } else {
+ mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+ }
+ }
+
/**
* Called when the nav/task bar is attached.
*/
@@ -1293,7 +1308,8 @@
} else {
mPackageName = "_UNKNOWN";
}
- return topActivity != null && mGestureBlockingActivities.contains(topActivity);
+
+ return topActivity != null && mGestureInteractor.areGesturesBlocked(topActivity);
}
public void setBackAnimation(BackAnimation backAnimation) {
@@ -1342,6 +1358,10 @@
private final Provider<LightBarController> mLightBarControllerProvider;
private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final GestureInteractor mGestureInteractor;
+
+ private final JavaAdapter mJavaAdapter;
+
@Inject
public Factory(OverviewProxyService overviewProxyService,
SysUiState sysUiState,
@@ -1361,8 +1381,10 @@
FalsingManager falsingManager,
Provider<BackGestureTfClassifierProvider>
backGestureTfClassifierProviderProvider,
- Provider<LightBarController> lightBarControllerProvider,
- NotificationShadeWindowController notificationShadeWindowController) {
+ Provider<LightBarController> lightBarControllerProvider,
+ NotificationShadeWindowController notificationShadeWindowController,
+ GestureInteractor gestureInteractor,
+ JavaAdapter javaAdapter) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -1382,6 +1404,8 @@
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
mLightBarControllerProvider = lightBarControllerProvider;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mGestureInteractor = gestureInteractor;
+ mJavaAdapter = javaAdapter;
}
/** Construct a {@link EdgeBackGestureHandler}. */
@@ -1407,7 +1431,9 @@
mFalsingManager,
mBackGestureTfClassifierProviderProvider,
mLightBarControllerProvider,
- mNotificationShadeWindowController));
+ mNotificationShadeWindowController,
+ mGestureInteractor,
+ mJavaAdapter));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/dagger/GestureModule.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/dagger/GestureModule.kt
new file mode 100644
index 0000000..72a84f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/dagger/GestureModule.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** {@link Module} for gesture related dependencies */
+@Module
+interface GestureModule {
+ /** */
+ @Binds fun gestureRespoitory(impl: GestureRepositoryImpl): GestureRepository
+}
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
new file mode 100644
index 0000000..8f35343
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/data/respository/GestureRepository.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.data.respository
+
+import android.content.ComponentName
+import android.util.ArraySet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+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>>
+
+ /** Adds an activity to be blocked from gestures. */
+ suspend fun addGestureBlockedActivity(activity: ComponentName)
+
+ /** Removes an activity from being blocked from gestures. */
+ suspend fun removeGestureBlockedActivity(activity: ComponentName)
+}
+
+@SysUISingleton
+class GestureRepositoryImpl
+@Inject
+constructor(@Main private val mainDispatcher: CoroutineDispatcher) : GestureRepository {
+ private val _gestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(ArraySet())
+
+ override val gestureBlockedActivities: StateFlow<Set<ComponentName>>
+ get() = _gestureBlockedActivities
+
+ override suspend fun addGestureBlockedActivity(activity: ComponentName) =
+ withContext(mainDispatcher) {
+ _gestureBlockedActivities.emit(
+ _gestureBlockedActivities.value.toMutableSet().apply { add(activity) }
+ )
+ }
+
+ override suspend fun removeGestureBlockedActivity(activity: ComponentName) =
+ withContext(mainDispatcher) {
+ _gestureBlockedActivities.emit(
+ _gestureBlockedActivities.value.toMutableSet().apply { remove(activity) }
+ )
+ }
+}
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
new file mode 100644
index 0000000..6dc5939
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt
@@ -0,0 +1,103 @@
+/*
+ * 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
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * {@link GestureInteractor} helps interact with gesture-related logic, including accessing the
+ * underlying {@link GestureRepository}.
+ */
+class GestureInteractor
+@Inject
+constructor(
+ private val gestureRepository: GestureRepository,
+ @Application private val scope: CoroutineScope
+) {
+ enum class Scope {
+ Local,
+ Global
+ }
+
+ private val _localGestureBlockedActivities = MutableStateFlow<Set<ComponentName>>(setOf())
+ /** A {@link StateFlow} for listening to changes in Activities where gestures are blocked */
+ val gestureBlockedActivities: StateFlow<Set<ComponentName>>
+ get() =
+ combine(
+ gestureRepository.gestureBlockedActivities,
+ _localGestureBlockedActivities.asStateFlow()
+ ) { global, local ->
+ global + local
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), setOf())
+
+ /**
+ * Adds an {@link Activity} to be blocked based on component when the topmost, focused {@link
+ * Activity}.
+ */
+ fun addGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) {
+ scope.launch {
+ when (gestureScope) {
+ Scope.Local -> {
+ _localGestureBlockedActivities.emit(
+ _localGestureBlockedActivities.value.toMutableSet().apply { add(activity) }
+ )
+ }
+ Scope.Global -> {
+ gestureRepository.addGestureBlockedActivity(activity)
+ }
+ }
+ }
+ }
+
+ /** Removes an {@link Activity} from being blocked from gestures. */
+ fun removeGestureBlockedActivity(activity: ComponentName, gestureScope: Scope) {
+ scope.launch {
+ when (gestureScope) {
+ Scope.Local -> {
+ _localGestureBlockedActivities.emit(
+ _localGestureBlockedActivities.value.toMutableSet().apply {
+ remove(activity)
+ }
+ )
+ }
+ Scope.Global -> {
+ gestureRepository.removeGestureBlockedActivity(activity)
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks whether the specified {@link Activity} {@link ComponentName} is being blocked from
+ * gestures.
+ */
+ fun areGesturesBlocked(activity: ComponentName): Boolean {
+ return gestureBlockedActivities.value.contains(activity)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt
new file mode 100644
index 0000000..9bd346e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/data/GestureRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.keyguard.gesture.data
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepository
+import com.android.systemui.navigationbar.gestural.data.respository.GestureRepositoryImpl
+
+val Kosmos.gestureRepository: GestureRepository by
+ Kosmos.Fixture { GestureRepositoryImpl(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt
new file mode 100644
index 0000000..658aaa6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/gesture/domain/GestureInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.keyguard.gesture.domain
+
+import com.android.systemui.keyguard.gesture.data.gestureRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.navigationbar.gestural.domain.GestureInteractor
+
+val Kosmos.gestureInteractor: GestureInteractor by
+ Kosmos.Fixture {
+ GestureInteractor(gestureRepository = gestureRepository, scope = applicationCoroutineScope)
+ }