Loading packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -1996,6 +1996,16 @@ flag { } } flag { name: "dream_preview_tap_dismiss" namespace: "systemui" description: "Dismisses dream in preview on tap" bug: "416412668" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "hub_blurred_by_shade_fix" namespace: "systemui" Loading packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +11 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Flags; import com.android.systemui.ambient.touch.TouchHandler; import com.android.systemui.ambient.touch.TouchMonitor; import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent; Loading @@ -72,6 +73,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent; import com.android.systemui.dreams.dagger.DreamModule; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.touch.DismissTouchHandler; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.navigationbar.gestural.domain.GestureInteractor; import com.android.systemui.navigationbar.gestural.domain.TaskMatcher; Loading Loading @@ -548,6 +550,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // to hub swipe gesture. touchHandlers.add(dreamOverlayComponent.getCommunalTouchHandler()); } if (isDreamInPreviewMode() && Flags.dreamPreviewTapDismiss()) { touchHandlers.add(new DismissTouchHandler(new DismissTouchHandler.DismissCallback() { @Override public void onDismissed() { mExecutor.execute(DreamOverlayService.this::requestExit); } })); } final AmbientTouchComponent ambientTouchComponent = mAmbientTouchComponentFactory.create( mLifecycleOwner, new HashSet<>(touchHandlers), TAG, SURFACE_DREAM); Loading packages/SystemUI/src/com/android/systemui/dreams/touch/DismissTouchHandler.kt 0 → 100644 +57 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.dreams.touch import android.view.GestureDetector.SimpleOnGestureListener import android.view.InputEvent import android.view.MotionEvent import com.android.systemui.ambient.touch.TouchHandler import com.android.systemui.ambient.touch.TouchHandler.TouchSession /** A simple {@link TouchHandler} that consumes touch down and informs callback on touch up. */ class DismissTouchHandler(val dismissCallback: DismissCallback) : TouchHandler { override fun onSessionStart(session: TouchSession) { session.registerGestureListener( object : SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { session.registerInputListener { ev: InputEvent? -> if (ev !is MotionEvent) { return@registerInputListener } if ( ev.action == MotionEvent.ACTION_CANCEL || ev.action == MotionEvent.ACTION_UP ) { session.pop() if (ev.action == MotionEvent.ACTION_UP) { dismissCallback.onDismissed() } } } return true } } ) } /** Callback to be informed of dismiss event. */ interface DismissCallback { /** Invoked on dismiss action. */ fun onDismissed() } } packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DismissTouchHandlerTest.kt 0 → 100644 +84 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.dreams.touch import android.view.GestureDetector.OnGestureListener import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.ambient.touch.TouchHandler import com.android.systemui.shared.system.InputChannelCompat import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class DismissTouchHandlerTest : SysuiTestCase() { @Mock private lateinit var touchHandlerCallback: DismissTouchHandler.DismissCallback @Mock private lateinit var touchSession: TouchHandler.TouchSession @Before fun setup() { MockitoAnnotations.initMocks(this) } private fun prepareTouchHandler(touchHandler: TouchHandler): OnGestureListener { touchHandler.onSessionStart(touchSession) val listenerCaptor = argumentCaptor<OnGestureListener>() verify(touchSession).registerGestureListener(listenerCaptor.capture()) return listenerCaptor.lastValue } private fun sendDownEvent( gestureListener: OnGestureListener ): InputChannelCompat.InputEventListener { val event = mock<MotionEvent>() whenever(event.action).thenReturn(MotionEvent.ACTION_DOWN) val listenerCaptor = argumentCaptor<InputChannelCompat.InputEventListener>() assertThat(gestureListener.onDown(event)).isTrue() verify(touchSession).registerInputListener(listenerCaptor.capture()) return listenerCaptor.lastValue } @Test fun dismissTouchHandlerConsumesTouch() { val touchHandler = DismissTouchHandler(touchHandlerCallback) val listener = prepareTouchHandler(touchHandler) assertThat(sendDownEvent(listener)).isNotNull() } @Test fun dismissTouchHandlerInformsCallback() { val touchHandler = DismissTouchHandler(touchHandlerCallback) val listener = sendDownEvent(prepareTouchHandler(touchHandler)) val event = mock<MotionEvent>() whenever(event.action).thenReturn(MotionEvent.ACTION_UP) listener.onInputEvent(event) verify(touchHandlerCallback).onDismissed() } } Loading
packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -1996,6 +1996,16 @@ flag { } } flag { name: "dream_preview_tap_dismiss" namespace: "systemui" description: "Dismisses dream in preview on tap" bug: "416412668" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "hub_blurred_by_shade_fix" namespace: "systemui" Loading
packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +11 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Flags; import com.android.systemui.ambient.touch.TouchHandler; import com.android.systemui.ambient.touch.TouchMonitor; import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent; Loading @@ -72,6 +73,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.complication.dagger.DreamComplicationComponent; import com.android.systemui.dreams.dagger.DreamModule; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.touch.DismissTouchHandler; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.navigationbar.gestural.domain.GestureInteractor; import com.android.systemui.navigationbar.gestural.domain.TaskMatcher; Loading Loading @@ -548,6 +550,15 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // to hub swipe gesture. touchHandlers.add(dreamOverlayComponent.getCommunalTouchHandler()); } if (isDreamInPreviewMode() && Flags.dreamPreviewTapDismiss()) { touchHandlers.add(new DismissTouchHandler(new DismissTouchHandler.DismissCallback() { @Override public void onDismissed() { mExecutor.execute(DreamOverlayService.this::requestExit); } })); } final AmbientTouchComponent ambientTouchComponent = mAmbientTouchComponentFactory.create( mLifecycleOwner, new HashSet<>(touchHandlers), TAG, SURFACE_DREAM); Loading
packages/SystemUI/src/com/android/systemui/dreams/touch/DismissTouchHandler.kt 0 → 100644 +57 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.dreams.touch import android.view.GestureDetector.SimpleOnGestureListener import android.view.InputEvent import android.view.MotionEvent import com.android.systemui.ambient.touch.TouchHandler import com.android.systemui.ambient.touch.TouchHandler.TouchSession /** A simple {@link TouchHandler} that consumes touch down and informs callback on touch up. */ class DismissTouchHandler(val dismissCallback: DismissCallback) : TouchHandler { override fun onSessionStart(session: TouchSession) { session.registerGestureListener( object : SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { session.registerInputListener { ev: InputEvent? -> if (ev !is MotionEvent) { return@registerInputListener } if ( ev.action == MotionEvent.ACTION_CANCEL || ev.action == MotionEvent.ACTION_UP ) { session.pop() if (ev.action == MotionEvent.ACTION_UP) { dismissCallback.onDismissed() } } } return true } } ) } /** Callback to be informed of dismiss event. */ interface DismissCallback { /** Invoked on dismiss action. */ fun onDismissed() } }
packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DismissTouchHandlerTest.kt 0 → 100644 +84 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.dreams.touch import android.view.GestureDetector.OnGestureListener import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.ambient.touch.TouchHandler import com.android.systemui.shared.system.InputChannelCompat import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class DismissTouchHandlerTest : SysuiTestCase() { @Mock private lateinit var touchHandlerCallback: DismissTouchHandler.DismissCallback @Mock private lateinit var touchSession: TouchHandler.TouchSession @Before fun setup() { MockitoAnnotations.initMocks(this) } private fun prepareTouchHandler(touchHandler: TouchHandler): OnGestureListener { touchHandler.onSessionStart(touchSession) val listenerCaptor = argumentCaptor<OnGestureListener>() verify(touchSession).registerGestureListener(listenerCaptor.capture()) return listenerCaptor.lastValue } private fun sendDownEvent( gestureListener: OnGestureListener ): InputChannelCompat.InputEventListener { val event = mock<MotionEvent>() whenever(event.action).thenReturn(MotionEvent.ACTION_DOWN) val listenerCaptor = argumentCaptor<InputChannelCompat.InputEventListener>() assertThat(gestureListener.onDown(event)).isTrue() verify(touchSession).registerInputListener(listenerCaptor.capture()) return listenerCaptor.lastValue } @Test fun dismissTouchHandlerConsumesTouch() { val touchHandler = DismissTouchHandler(touchHandlerCallback) val listener = prepareTouchHandler(touchHandler) assertThat(sendDownEvent(listener)).isNotNull() } @Test fun dismissTouchHandlerInformsCallback() { val touchHandler = DismissTouchHandler(touchHandlerCallback) val listener = sendDownEvent(prepareTouchHandler(touchHandler)) val event = mock<MotionEvent>() whenever(event.action).thenReturn(MotionEvent.ACTION_UP) listener.onInputEvent(event) verify(touchHandlerCallback).onDismissed() } }