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

Commit aee50c35 authored by Steven Ng's avatar Steven Ng
Browse files

Register notification panel key gesture handler in SysUi

Reasons to move away from the existing PhoneWindowManager pipeline:
1. the existing pipeline has too many redirection. It is less performant compared to the KeyGestureHandler approach.
2. the existing pipeline does not expose the focus display ID from the Input framework. However, the KeyGestureEvent does contain this ID. While we don't intend to use the Input framework's focus display ID at this time, it's beneficial to retain access to it for potential future use.

Test: atest WmTests:PhoneWindowManagerTests
Test: atest SystemUITests:SysUIKeyGestureEventInitializerTest
Flag: com.android.window.flags.enable_key_gesture_handler_for_sysui
Bug: 406740557
Change-Id: I7ba732d65633696fb3abbc6ad8dddb74a41ee228
parent f04795c6
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -1137,6 +1137,13 @@ flag {
    bug: "406452076"
}

flag {
    name: "enable_key_gesture_handler_for_sysui"
    namespace: "lse_desktop_experience"
    description: "Enables the key gesture handler for listening to SysUi interested key events."
    bug: "406740557"
}

flag {
    name: "enable_desktop_first_based_default_to_desktop_bugfix"
    namespace: "lse_desktop_experience"
+111 −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.keyevent

import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyGestureEventHandler
import android.hardware.input.KeyGestureEvent
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
import com.android.window.flags.Flags
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions

@SmallTest
@RunWith(AndroidJUnit4::class)
class SysUIKeyGestureEventInitializerTest : SysuiTestCase() {
    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
    @Mock private lateinit var inputManager: InputManager
    @Mock private lateinit var commandQueue: CommandQueue
    @Captor private lateinit var keyGestureEventsCaptor: ArgumentCaptor<List<Int>>
    @Captor
    private lateinit var keyGestureEventHandlerCaptor: ArgumentCaptor<KeyGestureEventHandler>

    private lateinit var underTest: SysUIKeyGestureEventInitializer

    @Before
    fun setup() {
        underTest = SysUIKeyGestureEventInitializer(inputManager, commandQueue)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_SYSUI)
    fun start_flagEnabled_registerKeyGestureEvents() {
        underTest.start()

        verify(inputManager).registerKeyGestureEventHandler(keyGestureEventsCaptor.capture(), any())
        keyGestureEventsCaptor.value.let { keyGestureEvents ->
            assertThat(keyGestureEvents).containsExactly(KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL)
        }
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_SYSUI)
    fun start_flagDisabled_noRegisterKeyGestureEvents() {
        underTest.start()

        verifyNoInteractions(inputManager)
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_SYSUI)
    fun handleKeyGestureEvent_eventTypeToggleNotificationPanel_toggleNotificationPanel() {
        underTest.start()
        verify(inputManager)
            .registerKeyGestureEventHandler(any(), keyGestureEventHandlerCaptor.capture())

        keyGestureEventHandlerCaptor.value.handleKeyGestureEvent(
            KeyGestureEvent.Builder()
                .setKeyGestureType(KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL)
                .build(),
            /* focusedToken= */ null,
        )

        verify(commandQueue).toggleNotificationsPanel()
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_KEY_GESTURE_HANDLER_FOR_SYSUI)
    fun handleKeyGestureEvent_otherEventTypeToggleNotificationPanel_noInteraction() {
        underTest.start()
        verify(inputManager)
            .registerKeyGestureEventHandler(any(), keyGestureEventHandlerCaptor.capture())

        keyGestureEventHandlerCaptor.value.handleKeyGestureEvent(
            KeyGestureEvent.Builder().setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS).build(),
            /* focusedToken= */ null,
        )

        verifyNoInteractions(commandQueue)
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.haptics.msdl.MSDLCoreStartable
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
import com.android.systemui.keyevent.SysUIKeyGestureEventInitializer
import com.android.systemui.keyguard.KeyguardViewConfigurator
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
@@ -346,4 +347,11 @@ abstract class SystemUICoreStartableModule {
    @IntoMap
    @ClassKey(ComplicationTypesUpdater::class)
    abstract fun bindComplicationTypesUpdater(updater: ComplicationTypesUpdater): CoreStartable

    @Binds
    @IntoMap
    @ClassKey(SysUIKeyGestureEventInitializer::class)
    abstract fun bindSysUIKeyGestureEventInitializer(
        keyGestureEventInitializer: SysUIKeyGestureEventInitializer
    ): CoreStartable
}
+56 −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.keyevent

import android.hardware.input.InputManager
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
import android.util.Slog
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.CommandQueue
import com.android.window.flags.Flags
import javax.inject.Inject

/**
 * Registers system UI interested keyboard shortcut events and dispatches events to the correct
 * handlers.
 */
@SysUISingleton
class SysUIKeyGestureEventInitializer
@Inject
constructor(private val inputManager: InputManager, private val commandQueue: CommandQueue) :
    CoreStartable {
    override fun start() {
        if (!Flags.enableKeyGestureHandlerForSysui()) {
            return
        }
        inputManager.registerKeyGestureEventHandler(
            listOf(KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL)
        ) { event, _ ->
            when (event.keyGestureType) {
                KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL ->
                    commandQueue.toggleNotificationsPanel()

                else -> Slog.w(TAG, "Unsupported key gesture event: ${event.keyGestureType}")
            }
        }
    }

    private companion object {
        const val TAG = "KeyGestureEventInitializer"
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -3385,7 +3385,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
                KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
                KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
@@ -3420,6 +3419,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            // recents app.
            supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
        }
        if (!com.android.window.flags.Flags.enableKeyGestureHandlerForSysui()) {
            supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
        }
        mInputManager.registerKeyGestureEventHandler(supportedGestures,
                PhoneWindowManager.this::handleKeyGestureEvent);
    }
Loading