1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.virtualization.terminal 18 19 import android.content.Context 20 import android.hardware.input.InputManager 21 import android.os.Handler 22 import android.system.virtualmachine.VirtualMachine 23 import android.util.Log 24 import android.view.InputDevice 25 import android.view.KeyEvent 26 import android.view.MotionEvent 27 import android.view.View 28 import com.android.virtualization.terminal.MainActivity.Companion.TAG 29 30 /** Forwards input events (touch, mouse, ...) from Android to VM */ 31 internal class InputForwarder( 32 private val context: Context, 33 vm: VirtualMachine, 34 touchReceiver: View, 35 mouseReceiver: View, 36 keyReceiver: View, 37 ) { 38 private val virtualMachine: VirtualMachine = vm 39 private var inputDeviceListener: InputManager.InputDeviceListener? = null 40 private var isTabletMode = false 41 42 init { 43 val config = vm.config.customImageConfig 44 45 checkNotNull(config) 46 47 if (config.useTouch() == true) { 48 setupTouchReceiver(touchReceiver) 49 } 50 if (config.useMouse() || config.useTrackpad()) { 51 setupMouseReceiver(mouseReceiver) 52 } 53 if (config.useKeyboard()) { 54 setupKeyReceiver(keyReceiver) 55 } 56 if (config.useSwitches()) { 57 // Any view's handler is fine. 58 setupTabletModeHandler(touchReceiver.getHandler()) 59 } 60 } 61 62 fun cleanUp() { 63 if (inputDeviceListener != null) { 64 val im = context.getSystemService<InputManager>(InputManager::class.java) 65 im.unregisterInputDeviceListener(inputDeviceListener) 66 inputDeviceListener = null 67 } 68 } 69 70 private fun setupTouchReceiver(receiver: View) { 71 receiver.setOnTouchListener( 72 View.OnTouchListener { v: View?, event: MotionEvent? -> 73 virtualMachine.sendMultiTouchEvent(event) 74 } 75 ) 76 } 77 78 private fun setupMouseReceiver(receiver: View) { 79 receiver.requestUnbufferedDispatch(InputDevice.SOURCE_ANY) 80 receiver.setOnCapturedPointerListener { v: View?, event: MotionEvent? -> 81 val eventSource = event!!.source 82 if ((eventSource and InputDevice.SOURCE_CLASS_POSITION) != 0) { 83 return@setOnCapturedPointerListener virtualMachine.sendTrackpadEvent(event) 84 } 85 virtualMachine.sendMouseEvent(event) 86 } 87 } 88 89 private fun setupKeyReceiver(receiver: View) { 90 receiver.setOnKeyListener { v: View?, code: Int, event: KeyEvent? -> 91 // TODO: this is guest-os specific. It shouldn't be handled here. 92 if (isVolumeKey(code)) { 93 return@setOnKeyListener false 94 } 95 virtualMachine.sendKeyEvent(event) 96 } 97 } 98 99 private fun setupTabletModeHandler(handler: Handler?) { 100 val im = context.getSystemService<InputManager?>(InputManager::class.java) 101 inputDeviceListener = 102 object : InputManager.InputDeviceListener { 103 override fun onInputDeviceAdded(deviceId: Int) { 104 setTabletModeConditionally() 105 } 106 107 override fun onInputDeviceRemoved(deviceId: Int) { 108 setTabletModeConditionally() 109 } 110 111 override fun onInputDeviceChanged(deviceId: Int) { 112 setTabletModeConditionally() 113 } 114 } 115 im!!.registerInputDeviceListener(inputDeviceListener, handler) 116 } 117 118 fun setTabletModeConditionally() { 119 val tabletModeNeeded = !hasPhysicalKeyboard() 120 if (tabletModeNeeded != isTabletMode) { 121 val mode = if (tabletModeNeeded) "tablet mode" else "desktop mode" 122 Log.d(TAG, "switching to $mode") 123 isTabletMode = tabletModeNeeded 124 virtualMachine.sendTabletModeEvent(tabletModeNeeded) 125 } 126 } 127 128 companion object { 129 private fun isVolumeKey(keyCode: Int): Boolean { 130 return keyCode == KeyEvent.KEYCODE_VOLUME_UP || 131 keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || 132 keyCode == KeyEvent.KEYCODE_VOLUME_MUTE 133 } 134 135 private fun hasPhysicalKeyboard(): Boolean { 136 for (id in InputDevice.getDeviceIds()) { 137 val d = InputDevice.getDevice(id) 138 if (!d!!.isVirtual && d.isEnabled && d.isFullKeyboard) { 139 return true 140 } 141 } 142 return false 143 } 144 } 145 } 146