• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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