Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bd950bd8 authored by Bryce Lee's avatar Bryce Lee Committed by Android (Google) Code Review
Browse files

Merge "Screensaver: Always dismiss on touch in preview mode." into main

parents b8e1cbaa c7061df4
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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"
+11 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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);

+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()
    }
}
+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()
    }
}