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

Commit a8b0a39e authored by Johannes Gallmann's avatar Johannes Gallmann
Browse files

[CD] Handle trackpad back gestures on connected displays

This CL enables trackpad back gesture handling on connected displays in
EdgeBackGestureHandler.

Bug: 382774299
Test: Manual, i.e. verified that trackpad back gestures work on
      connected displays.
Test: presubmit
Test: Automated tests in follow up CL
Flag: com.android.window.flags.enable_multidisplay_trackpad_back_gesture
Change-Id: I43d3a06c6c32fc2da1a3956bfe477a711e7aa17a
parent c1e90b43
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ public interface NavigationEdgeBackPlugin extends Plugin {
    void onMotionEvent(MotionEvent motionEvent);

    /** Dumps info about the back gesture plugin. */
    void dump(PrintWriter pw);
    void dump(String prefix, PrintWriter pw);

    /** Callback to let the system react to the detected back gestures. */
    interface BackCallback {
+3 −0
Original line number Diff line number Diff line
@@ -239,6 +239,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
    @Override
    public void onDisplayAddSystemDecorations(int displayId) {
        CommandQueue.Callbacks.super.onDisplayAddSystemDecorations(displayId);
        mEdgeBackGestureHandler.onDisplayAddSystemDecorations(displayId);
        if (mLauncherProxyService.getProxy() == null) {
            return;
        }
@@ -253,6 +254,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
    @Override
    public void onDisplayRemoved(int displayId) {
        CommandQueue.Callbacks.super.onDisplayRemoved(displayId);
        mEdgeBackGestureHandler.onDisplayRemoveSystemDecorations(displayId);
        if (mLauncherProxyService.getProxy() == null) {
            return;
        }
@@ -267,6 +269,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
    @Override
    public void onDisplayRemoveSystemDecorations(int displayId) {
        CommandQueue.Callbacks.super.onDisplayRemoveSystemDecorations(displayId);
        mEdgeBackGestureHandler.onDisplayRemoveSystemDecorations(displayId);
        if (mLauncherProxyService.getProxy() == null) {
            return;
        }
+10 −6
Original line number Diff line number Diff line
@@ -84,7 +84,7 @@ class BackPanelController
@AssistedInject
constructor(
    @Assisted context: Context,
    private val windowManager: WindowManager,
    @Assisted private val windowManager: WindowManager,
    private val viewConfiguration: ViewConfiguration,
    @Assisted private val mainHandler: Handler,
    private val systemClock: SystemClock,
@@ -96,7 +96,11 @@ constructor(

    @AssistedFactory
    interface Factory {
        fun create(context: Context, handler: Handler): BackPanelController
        fun create(
            context: Context,
            windowManager: WindowManager,
            handler: Handler,
        ): BackPanelController
    }

    @VisibleForTesting internal var params: EdgePanelParams = EdgePanelParams(resources)
@@ -1018,10 +1022,10 @@ constructor(
        updateArrowState(GestureState.GONE, force = true)
    }

    override fun dump(pw: PrintWriter) {
        pw.println("$TAG:")
        pw.println("  currentState=$currentState")
        pw.println("  isLeftPanel=${mView.isLeftPanel}")
    override fun dump(prefix: String, pw: PrintWriter) {
        pw.println("$prefix$TAG:")
        pw.println("$prefix  currentState=$currentState")
        pw.println("$prefix  isLeftPanel=${mView.isLeftPanel}")
    }

    @VisibleForTesting
+177 −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.navigationbar.gestural

import android.content.Context
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.graphics.Point
import android.os.Trace
import android.view.InputEvent
import android.view.MotionEvent
import android.view.WindowManager
import com.android.systemui.plugins.NavigationEdgeBackPlugin
import com.android.systemui.res.R
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.shared.system.InputMonitorCompat
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.BackPanelUiThread
import com.android.systemui.util.concurrency.UiThreadContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.io.PrintWriter

interface DisplayBackGestureHandler {

    fun onMotionEvent(ev: MotionEvent)

    fun setIsLeftPanel(isLeft: Boolean)

    fun setBatchingEnabled(enabled: Boolean)

    fun pilferPointers()

    fun dispose()

    fun dump(prefix: String, writer: PrintWriter)
}

class DisplayBackGestureHandlerImpl
@AssistedInject
constructor(
    @Assisted private val context: Context,
    @Assisted private val windowManager: WindowManager,
    @Assisted private val onInputEvent: (InputEvent) -> Unit,
    @Assisted backCallback: NavigationEdgeBackPlugin.BackCallback,
    @BackPanelUiThread private val uiThreadContext: UiThreadContext,
    private val backPanelControllerFactory: BackPanelController.Factory,
    configurationControllerFactory: ConfigurationControllerImpl.Factory,
) : DisplayBackGestureHandler {

    @AssistedFactory
    interface Factory {
        fun create(
            context: Context,
            windowManager: WindowManager,
            backCallback: NavigationEdgeBackPlugin.BackCallback,
            onInputEvent: (InputEvent) -> Unit,
        ): DisplayBackGestureHandlerImpl
    }

    private val displayId = context.displayId
    private val configurationController = configurationControllerFactory.create(context)
    private val displaySize =
        Point().apply {
            val bounds = windowManager.maximumWindowMetrics.bounds
            set(bounds.width(), bounds.height())
        }
    private val edgeBackPlugin = createEdgeBackPlugin(backCallback)

    private val inputMonitorCompat = InputMonitorCompat("edge-swipe", displayId)
    private val inputEventReceiver: InputChannelCompat.InputEventReceiver =
        inputMonitorCompat.getInputReceiver(
            uiThreadContext.looper,
            uiThreadContext.choreographer,
        ) { ev ->
            onInputEvent(ev)
        }

    private val configurationListener =
        object : ConfigurationController.ConfigurationListener {
            override fun onConfigChanged(newConfig: Configuration?) {
                newConfig?.windowConfiguration?.maxBounds?.let {
                    displaySize.set(it.width(), it.height())
                    edgeBackPlugin.setDisplaySize(displaySize)
                }
            }
        }

    init {
        configurationController.addCallback(configurationListener)
    }

    override fun onMotionEvent(ev: MotionEvent) = edgeBackPlugin.onMotionEvent(ev)

    override fun setIsLeftPanel(isLeft: Boolean) = edgeBackPlugin.setIsLeftPanel(isLeft)

    override fun setBatchingEnabled(enabled: Boolean) =
        inputEventReceiver.setBatchingEnabled(enabled)

    override fun pilferPointers() = inputMonitorCompat.pilferPointers()

    override fun dispose() {
        inputEventReceiver.dispose()
        inputMonitorCompat.dispose()
        edgeBackPlugin.onDestroy()
        configurationController.removeCallback(configurationListener)
    }

    private fun createEdgeBackPlugin(
        backCallback: NavigationEdgeBackPlugin.BackCallback
    ): BackPanelController {
        val backPanelController =
            backPanelControllerFactory.create(context, windowManager, uiThreadContext.handler)
        backPanelController.init()

        try {
            Trace.beginSection("setEdgeBackPlugin")
            backPanelController.setBackCallback(backCallback)
            backPanelController.setLayoutParams(createLayoutParams())
            backPanelController.setDisplaySize(displaySize)
        } finally {
            Trace.endSection()
        }
        return backPanelController
    }

    private fun createLayoutParams(): WindowManager.LayoutParams {
        val resources = context.resources
        val layoutParams =
            WindowManager.LayoutParams(
                resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
                resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN),
                PixelFormat.TRANSLUCENT,
            )
        layoutParams.accessibilityTitle = context.getString(R.string.nav_bar_edge_panel)
        layoutParams.windowAnimations = 0
        layoutParams.privateFlags =
            layoutParams.privateFlags or
                (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS or
                    WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION)
        layoutParams.title = "$TAG $displayId"
        layoutParams.fitInsetsTypes = 0
        layoutParams.setTrustedOverlay()
        return layoutParams
    }

    override fun dump(prefix: String, pw: PrintWriter) {
        pw.println("$prefix$TAG (displayId=$displayId)")
        pw.println("$prefix  displaySize=$displaySize")
        pw.println("$prefix  edgeBackPlugin=$edgeBackPlugin")
        edgeBackPlugin.dump("$prefix  ", pw)
    }

    companion object {
        private const val TAG = "DisplayBackGestureHandler"
    }
}
+192 −46

File changed.

Preview size limit exceeded, changes collapsed.