/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.react import android.view.KeyEvent import android.view.View import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.WritableNativeMap /** Responsible for dispatching events specific for hardware inputs. */ internal class ReactAndroidHWInputDeviceHelper { /** * We keep a reference to the last focused view id so that we can send it as a target for key * events and be able to send a blur event when focus changes. */ private var lastFocusedViewId = View.NO_ID /** Called from [ReactRootView]. This is the main place the key events are handled. */ fun handleKeyEvent(ev: KeyEvent, context: ReactContext) { val eventKeyCode = ev.keyCode val eventKeyAction = ev.action if ( (eventKeyAction == KeyEvent.ACTION_UP || eventKeyAction == KeyEvent.ACTION_DOWN) && KEY_EVENTS_ACTIONS.containsKey(eventKeyCode) ) { dispatchEvent(context, KEY_EVENTS_ACTIONS[eventKeyCode], lastFocusedViewId, eventKeyAction) } } /** Called from [ReactRootView] when focused view changes. */ fun onFocusChanged(newFocusedView: View, context: ReactContext) { if (lastFocusedViewId == newFocusedView.id) { return } if (lastFocusedViewId != View.NO_ID) { dispatchEvent(context, "blur", lastFocusedViewId) } lastFocusedViewId = newFocusedView.id dispatchEvent(context, "focus", newFocusedView.id) } /** Called from [ReactRootView] when the whole view hierarchy looses focus. */ fun clearFocus(context: ReactContext) { if (lastFocusedViewId != View.NO_ID) { dispatchEvent(context, "blur", lastFocusedViewId) } lastFocusedViewId = View.NO_ID } private fun dispatchEvent( context: ReactContext, eventType: String?, targetViewId: Int, eventKeyAction: Int = -1, ) { val event: WritableMap = WritableNativeMap().apply { putString("eventType", eventType) putInt("eventKeyAction", eventKeyAction) if (targetViewId != View.NO_ID) { putInt("tag", targetViewId) } } context.emitDeviceEvent("onHWKeyEvent", event) } private companion object { /** * Contains a mapping between handled KeyEvents and the corresponding navigation event that * should be fired when the KeyEvent is received. */ private val KEY_EVENTS_ACTIONS: Map = mapOf( KeyEvent.KEYCODE_DPAD_CENTER to "select", KeyEvent.KEYCODE_ENTER to "select", KeyEvent.KEYCODE_SPACE to "select", KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE to "playPause", KeyEvent.KEYCODE_MEDIA_REWIND to "rewind", KeyEvent.KEYCODE_MEDIA_FAST_FORWARD to "fastForward", KeyEvent.KEYCODE_MEDIA_STOP to "stop", KeyEvent.KEYCODE_MEDIA_NEXT to "next", KeyEvent.KEYCODE_MEDIA_PREVIOUS to "previous", KeyEvent.KEYCODE_DPAD_UP to "up", KeyEvent.KEYCODE_DPAD_RIGHT to "right", KeyEvent.KEYCODE_DPAD_DOWN to "down", KeyEvent.KEYCODE_DPAD_LEFT to "left", KeyEvent.KEYCODE_INFO to "info", KeyEvent.KEYCODE_MENU to "menu", KeyEvent.KEYCODE_CHANNEL_UP to "channelUp", KeyEvent.KEYCODE_CHANNEL_DOWN to "channelDown", ) } }