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

Commit 64f03b74 authored by Massimo Carli's avatar Massimo Carli Committed by Android (Google) Code Review
Browse files

Merge "[22/n] Event handling for reachability" into main

parents 6dd098ac 5f479e8f
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.wm.shell.common

import android.window.WindowContainerTransaction
import com.android.wm.shell.dagger.WMSingleton
import java.util.function.Supplier
import javax.inject.Inject

/**
 * An Injectable [Supplier<WindowContainerTransaction>]. This can be used in place of kotlin default
 * parameters values [builder = ::WindowContainerTransaction] which requires the
 * [@JvmOverloads] annotation to make this available in Java.
 * This can be used every time a component needs the dependency to the default [Supplier] for
 * [WindowContainerTransaction]s.
 */
@WMSingleton
class WindowContainerTransactionSupplier @Inject constructor(
) : Supplier<WindowContainerTransaction> {
    override fun get(): WindowContainerTransaction = WindowContainerTransaction()
}
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.wm.shell.compatui.letterbox.events

import android.graphics.Rect
import android.view.GestureDetector
import android.view.MotionEvent
import android.window.WindowContainerToken
import com.android.wm.shell.common.WindowContainerTransactionSupplier
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_LETTERBOX_REACHABILITY

/**
 * [GestureDetector.SimpleOnGestureListener] implementation which receives events from the
 * Letterbox Input surface, understands the type of event and filter them based on the current
 * letterbox position.
 */
class ReachabilityGestureListener(
    private val taskId: Int,
    private val token: WindowContainerToken?,
    private val transitions: Transitions,
    private val animationHandler: Transitions.TransitionHandler,
    private val wctSupplier: WindowContainerTransactionSupplier
) : GestureDetector.SimpleOnGestureListener() {

    // The current letterbox bounds. Double tap events are ignored when happening in these bounds.
    private val activityBounds = Rect()

    override fun onDoubleTap(e: MotionEvent): Boolean {
        val x = e.rawX.toInt()
        val y = e.rawY.toInt()
        if (!activityBounds.contains(x, y)) {
            val wct = wctSupplier.get().apply {
                setReachabilityOffset(token!!, taskId, x, y)
            }
            transitions.startTransition(
                TRANSIT_MOVE_LETTERBOX_REACHABILITY,
                wct,
                animationHandler
            )
            return true
        }
        return false
    }

    /**
     * Updates the bounds for the letterboxed activity.
     */
    fun updateActivityBounds(newActivityBounds: Rect) {
        activityBounds.set(newActivityBounds)
    }
}
+43 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.wm.shell.compatui.letterbox.events

import android.window.WindowContainerToken
import com.android.wm.shell.common.WindowContainerTransactionSupplier
import com.android.wm.shell.dagger.WMSingleton
import com.android.wm.shell.transition.Transitions
import javax.inject.Inject

/**
 * A Factory for [ReachabilityGestureListener].
 */
@WMSingleton
class ReachabilityGestureListenerFactory @Inject constructor(
    private val transitions: Transitions,
    private val animationHandler: Transitions.TransitionHandler,
    private val wctSupplier: WindowContainerTransactionSupplier
) {
    /**
     * @return a [ReachabilityGestureListener] implementation to listen to double tap events and
     * creating the related [WindowContainerTransaction] to handle the transition.
     */
    fun createReachabilityGestureListener(
        taskId: Int,
        token: WindowContainerToken?
    ): ReachabilityGestureListener =
        ReachabilityGestureListener(taskId, token, transitions, animationHandler, wctSupplier)
}
+42 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.wm.shell.common

import android.testing.AndroidTestingRunner
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Tests for [WindowContainerTransactionSupplier].
 *
 * Build/Install/Run:
 *  atest WMShellUnitTests:WindowContainerTransactionSupplierTest
 */
@RunWith(AndroidTestingRunner::class)
@SmallTest
class WindowContainerTransactionSupplierTest {

    @Test
    fun `WindowContainerTransactionSupplier supplies a WindowContainerTransaction`() {
        val supplier = WindowContainerTransactionSupplier()
        SuppliersUtilsTest.assertSupplierProvidesValue(supplier) {
            it is WindowContainerTransaction
        }
    }
}
+131 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.wm.shell.compatui.letterbox.events

import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.WindowContainerTransactionSupplier
import com.android.wm.shell.compatui.letterbox.LetterboxEvents.motionEventAt
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_MOVE_LETTERBOX_REACHABILITY
import java.util.function.Consumer
import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify

/**
 * Tests for [ReachabilityGestureListenerFactory].
 *
 * Build/Install/Run:
 *  atest WMShellUnitTests:ReachabilityGestureListenerFactoryTest
 */
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ReachabilityGestureListenerFactoryTest : ShellTestCase() {

    @Test
    fun `When invoked a ReachabilityGestureListenerFactory is created`() {
        runTestScenario { r ->
            r.invokeCreate()

            r.checkReachabilityGestureListenerCreated()
        }
    }

    @Test
    fun `Right parameters are used for creation`() {
        runTestScenario { r ->
            r.invokeCreate()

            r.checkRightParamsAreUsed()
        }
    }

    /**
     * Runs a test scenario providing a Robot.
     */
    fun runTestScenario(consumer: Consumer<ReachabilityGestureListenerFactoryRobotTest>) {
        val robot = ReachabilityGestureListenerFactoryRobotTest()
        consumer.accept(robot)
    }

    class ReachabilityGestureListenerFactoryRobotTest {

        companion object {
            @JvmStatic
            private val TASK_ID = 1

            @JvmStatic
            private val TOKEN = mock<WindowContainerToken>()
        }

        private val transitions: Transitions
        private val animationHandler: Transitions.TransitionHandler
        private val factory: ReachabilityGestureListenerFactory
        private val wctSupplier: WindowContainerTransactionSupplier
        private val wct: WindowContainerTransaction
        private lateinit var obtainedResult: Any

        init {
            transitions = mock<Transitions>()
            animationHandler = mock<Transitions.TransitionHandler>()
            wctSupplier = mock<WindowContainerTransactionSupplier>()
            wct = mock<WindowContainerTransaction>()
            doReturn(wct).`when`(wctSupplier).get()
            factory = ReachabilityGestureListenerFactory(transitions, animationHandler, wctSupplier)
        }

        fun invokeCreate(taskId: Int = TASK_ID, token: WindowContainerToken? = TOKEN) {
            obtainedResult = factory.createReachabilityGestureListener(taskId, token)
        }

        fun checkReachabilityGestureListenerCreated(expected: Boolean = true) {
            assertEquals(expected, obtainedResult is ReachabilityGestureListener)
        }

        fun checkRightParamsAreUsed(taskId: Int = TASK_ID, token: WindowContainerToken? = TOKEN) {
            with(obtainedResult as ReachabilityGestureListener) {
                // Click outside the bounds
                updateActivityBounds(Rect(0, 0, 10, 20))
                onDoubleTap(motionEventAt(50f, 100f))
                // WindowContainerTransactionSupplier is invoked to create a
                // WindowContainerTransaction
                verify(wctSupplier).get()
                // Verify the right params are passed to startAppCompatReachability()
                verify(wct).setReachabilityOffset(
                    token!!,
                    taskId,
                    50,
                    100
                )
                // startTransition() is invoked on Transitions with the right parameters
                verify(transitions).startTransition(
                    TRANSIT_MOVE_LETTERBOX_REACHABILITY,
                    wct,
                    animationHandler
                )
            }
        }
    }
}
Loading