1 /* 2 * Copyright (C) 2023 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.example.android.vdmdemo.client; 18 19 import android.util.Log; 20 import android.view.Display; 21 import android.view.InputEvent; 22 import android.view.KeyEvent; 23 import android.view.MotionEvent; 24 25 import androidx.annotation.GuardedBy; 26 27 import com.example.android.vdmdemo.common.RemoteEventProto.DisplayChangeEvent; 28 import com.example.android.vdmdemo.common.RemoteEventProto.InputDeviceType; 29 import com.example.android.vdmdemo.common.RemoteEventProto.RemoteEvent; 30 import com.example.android.vdmdemo.common.RemoteEventProto.RemoteHomeEvent; 31 import com.example.android.vdmdemo.common.RemoteEventProto.RemoteInputEvent; 32 import com.example.android.vdmdemo.common.RemoteEventProto.RemoteKeyEvent; 33 import com.example.android.vdmdemo.common.RemoteEventProto.RemoteMotionEvent; 34 import com.example.android.vdmdemo.common.RemoteIo; 35 import com.google.common.collect.Iterables; 36 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Set; 42 43 import javax.inject.Inject; 44 import javax.inject.Singleton; 45 46 /** Maintains focused display and handles injection of targeted and untargeted input events. */ 47 @Singleton 48 final class InputManager { 49 private static final String TAG = "InputManager"; 50 51 private final RemoteIo mRemoteIo; 52 53 private final Object mLock = new Object(); 54 55 @GuardedBy("mLock") 56 private int mFocusedDisplayId = Display.INVALID_DISPLAY; 57 58 interface FocusListener { onFocusChange(int focusedDisplayId)59 void onFocusChange(int focusedDisplayId); 60 } 61 62 @GuardedBy("mLock") 63 private final List<FocusListener> mFocusListeners = new ArrayList<>(); 64 65 @GuardedBy("mLock") 66 private final Set<Integer> mFocusableDisplays = new HashSet<>(); 67 68 @Inject InputManager(RemoteIo remoteIo)69 InputManager(RemoteIo remoteIo) { 70 mRemoteIo = remoteIo; 71 } 72 addFocusListener(FocusListener focusListener)73 void addFocusListener(FocusListener focusListener) { 74 synchronized (mLock) { 75 mFocusListeners.add(focusListener); 76 } 77 } 78 removeFocusListener(FocusListener focusListener)79 void removeFocusListener(FocusListener focusListener) { 80 synchronized (mLock) { 81 mFocusListeners.remove(focusListener); 82 } 83 } 84 addFocusableDisplay(int displayId)85 void addFocusableDisplay(int displayId) { 86 synchronized (mLock) { 87 if (mFocusableDisplays.add(displayId)) { 88 setFocusedDisplayId(displayId); 89 } 90 } 91 } 92 removeFocusableDisplay(int displayId)93 void removeFocusableDisplay(int displayId) { 94 synchronized (mLock) { 95 mFocusableDisplays.remove(displayId); 96 if (displayId == mFocusedDisplayId) { 97 setFocusedDisplayId(updateFocusedDisplayId()); 98 } 99 } 100 } 101 102 /** Injects {@link InputEvent} for the given {@link InputDeviceType} into the given display. */ sendInputEvent(InputDeviceType deviceType, InputEvent inputEvent, int displayId)103 void sendInputEvent(InputDeviceType deviceType, InputEvent inputEvent, int displayId) { 104 if (inputEvent instanceof MotionEvent) { 105 MotionEvent event = (MotionEvent) inputEvent; 106 switch (deviceType) { 107 case DEVICE_TYPE_NAVIGATION_TOUCHPAD: 108 case DEVICE_TYPE_TOUCHSCREEN: 109 sendTouchEvent(deviceType, event, displayId); 110 break; 111 case DEVICE_TYPE_MOUSE: 112 sendMouseEvent(event, displayId); 113 break; 114 case DEVICE_TYPE_ROTARY_ENCODER: 115 sendRotaryEvent(event, displayId); 116 break; 117 default: 118 Log.e(TAG, "sendInputEvent got invalid device type " + deviceType.getNumber()); 119 } 120 } else { 121 KeyEvent event = (KeyEvent) inputEvent; 122 sendKeyEvent(deviceType, event, displayId); 123 } 124 } 125 126 /** 127 * Injects {@link InputEvent} for the given {@link InputDeviceType} into the focused display. 128 * 129 * @return whether the event was sent. 130 */ sendInputEventToFocusedDisplay( InputDeviceType deviceType, InputEvent inputEvent)131 public boolean sendInputEventToFocusedDisplay( 132 InputDeviceType deviceType, InputEvent inputEvent) { 133 int targetDisplayId; 134 synchronized (mLock) { 135 if (mFocusedDisplayId == Display.INVALID_DISPLAY) { 136 return false; 137 } 138 targetDisplayId = mFocusedDisplayId; 139 } 140 sendInputEvent(deviceType, inputEvent, targetDisplayId); 141 return true; 142 } 143 sendBack(int displayId)144 void sendBack(int displayId) { 145 setFocusedDisplayId(displayId); 146 for (int action : new int[] {KeyEvent.ACTION_DOWN, KeyEvent.ACTION_UP}) { 147 sendInputEvent( 148 RemoteInputEvent.newBuilder() 149 .setDeviceType(InputDeviceType.DEVICE_TYPE_DPAD) 150 .setKeyEvent( 151 RemoteKeyEvent.newBuilder() 152 .setAction(action) 153 .setKeyCode(KeyEvent.KEYCODE_BACK)) 154 .build(), 155 displayId); 156 } 157 } 158 sendHome(int displayId)159 void sendHome(int displayId) { 160 setFocusedDisplayId(displayId); 161 mRemoteIo.sendMessage( 162 RemoteEvent.newBuilder() 163 .setDisplayId(displayId) 164 .setHomeEvent(RemoteHomeEvent.newBuilder()) 165 .build()); 166 } 167 sendTouchEvent(InputDeviceType deviceType, MotionEvent event, int displayId)168 private void sendTouchEvent(InputDeviceType deviceType, MotionEvent event, int displayId) { 169 setFocusedDisplayId(displayId); 170 for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); pointerIndex++) { 171 sendInputEvent( 172 RemoteInputEvent.newBuilder() 173 .setDeviceType(deviceType) 174 .setTimestampMs(event.getEventTime()) 175 .setTouchEvent( 176 RemoteMotionEvent.newBuilder() 177 .setPointerId(event.getPointerId(pointerIndex)) 178 .setAction(event.getActionMasked()) 179 .setX(event.getX(pointerIndex)) 180 .setY(event.getY(pointerIndex)) 181 .setPressure(event.getPressure(pointerIndex))) 182 .build(), 183 displayId); 184 } 185 } 186 sendKeyEvent(InputDeviceType deviceType, KeyEvent event, int displayId)187 private void sendKeyEvent(InputDeviceType deviceType, KeyEvent event, int displayId) { 188 sendInputEvent( 189 RemoteInputEvent.newBuilder() 190 .setDeviceType(deviceType) 191 .setTimestampMs(event.getEventTime()) 192 .setKeyEvent( 193 RemoteKeyEvent.newBuilder() 194 .setAction(event.getAction()) 195 .setKeyCode(event.getKeyCode()) 196 .build()) 197 .build(), 198 displayId); 199 } 200 sendMouseEvent(MotionEvent event, int displayId)201 private void sendMouseEvent(MotionEvent event, int displayId) { 202 switch (event.getAction()) { 203 case MotionEvent.ACTION_BUTTON_PRESS: 204 case MotionEvent.ACTION_BUTTON_RELEASE: 205 RemoteInputEvent buttonEvent = 206 RemoteInputEvent.newBuilder() 207 .setTimestampMs(event.getEventTime()) 208 .setDeviceType(InputDeviceType.DEVICE_TYPE_MOUSE) 209 .setMouseButtonEvent( 210 RemoteKeyEvent.newBuilder() 211 .setAction(event.getAction()) 212 .setKeyCode(event.getActionButton()) 213 .build()) 214 .build(); 215 sendInputEvent(buttonEvent, displayId); 216 break; 217 case MotionEvent.ACTION_HOVER_ENTER: 218 case MotionEvent.ACTION_HOVER_EXIT: 219 case MotionEvent.ACTION_HOVER_MOVE: 220 setFocusedDisplayId(displayId); 221 RemoteInputEvent relativeEvent = 222 RemoteInputEvent.newBuilder() 223 .setTimestampMs(event.getEventTime()) 224 .setDeviceType(InputDeviceType.DEVICE_TYPE_MOUSE) 225 .setMouseRelativeEvent( 226 RemoteMotionEvent.newBuilder() 227 .setX(event.getX()) 228 .setY(event.getY()) 229 .build()) 230 .build(); 231 sendInputEvent(relativeEvent, displayId); 232 break; 233 case MotionEvent.ACTION_SCROLL: 234 float scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 235 float scrollY = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 236 RemoteInputEvent scrollEvent = 237 RemoteInputEvent.newBuilder() 238 .setTimestampMs(event.getEventTime()) 239 .setDeviceType(InputDeviceType.DEVICE_TYPE_MOUSE) 240 .setMouseScrollEvent( 241 RemoteMotionEvent.newBuilder() 242 .setX(clampMouseScroll(scrollX)) 243 .setY(clampMouseScroll(scrollY)) 244 .build()) 245 .build(); 246 sendInputEvent(scrollEvent, displayId); 247 break; 248 } 249 } 250 sendRotaryEvent(MotionEvent event, int displayId)251 private void sendRotaryEvent(MotionEvent event, int displayId) { 252 sendInputEvent( 253 RemoteInputEvent.newBuilder() 254 .setDeviceType(InputDeviceType.DEVICE_TYPE_ROTARY_ENCODER) 255 .setTimestampMs(event.getEventTime()) 256 .setMouseScrollEvent(RemoteMotionEvent.newBuilder() 257 .setX(event.getAxisValue(MotionEvent.AXIS_SCROLL)) 258 .build()) 259 .build(), 260 displayId); 261 } 262 sendInputEvent(RemoteInputEvent inputEvent, int displayId)263 private void sendInputEvent(RemoteInputEvent inputEvent, int displayId) { 264 mRemoteIo.sendMessage( 265 RemoteEvent.newBuilder().setDisplayId(displayId).setInputEvent(inputEvent).build()); 266 } 267 clampMouseScroll(float val)268 private static float clampMouseScroll(float val) { 269 return Math.max(Math.min(val, 1f), -1f); 270 } 271 updateFocusedDisplayId()272 private int updateFocusedDisplayId() { 273 synchronized (mLock) { 274 if (mFocusableDisplays.contains(mFocusedDisplayId)) { 275 return mFocusedDisplayId; 276 } 277 return Iterables.getFirst(mFocusableDisplays, Display.INVALID_DISPLAY); 278 } 279 } 280 setFocusedDisplayId(int displayId)281 void setFocusedDisplayId(int displayId) { 282 List<FocusListener> listenersToNotify = Collections.emptyList(); 283 boolean focusedDisplayChanged = false; 284 synchronized (mLock) { 285 if (displayId != mFocusedDisplayId) { 286 mFocusedDisplayId = displayId; 287 listenersToNotify = new ArrayList<>(mFocusListeners); 288 focusedDisplayChanged = true; 289 } 290 } 291 if (focusedDisplayChanged) { 292 mRemoteIo.sendMessage(RemoteEvent.newBuilder() 293 .setDisplayId(displayId) 294 .setDisplayChangeEvent(DisplayChangeEvent.newBuilder().setFocused(true)) 295 .build()); 296 } 297 for (FocusListener focusListener : listenersToNotify) { 298 focusListener.onFocusChange(displayId); 299 } 300 } 301 } 302