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 default: 115 Log.e(TAG, "sendInputEvent got invalid device type " + deviceType.getNumber()); 116 } 117 } else { 118 KeyEvent event = (KeyEvent) inputEvent; 119 sendKeyEvent(deviceType, event, displayId); 120 } 121 } 122 123 /** 124 * Injects {@link InputEvent} for the given {@link InputDeviceType} into the focused display. 125 * 126 * @return whether the event was sent. 127 */ sendInputEventToFocusedDisplay( InputDeviceType deviceType, InputEvent inputEvent)128 public boolean sendInputEventToFocusedDisplay( 129 InputDeviceType deviceType, InputEvent inputEvent) { 130 int targetDisplayId; 131 synchronized (mLock) { 132 if (mFocusedDisplayId == Display.INVALID_DISPLAY) { 133 return false; 134 } 135 targetDisplayId = mFocusedDisplayId; 136 } 137 sendInputEvent(deviceType, inputEvent, targetDisplayId); 138 return true; 139 } 140 sendBack(int displayId)141 void sendBack(int displayId) { 142 setFocusedDisplayId(displayId); 143 for (int action : new int[] {KeyEvent.ACTION_DOWN, KeyEvent.ACTION_UP}) { 144 sendInputEvent( 145 RemoteInputEvent.newBuilder() 146 .setDeviceType(InputDeviceType.DEVICE_TYPE_DPAD) 147 .setKeyEvent( 148 RemoteKeyEvent.newBuilder() 149 .setAction(action) 150 .setKeyCode(KeyEvent.KEYCODE_BACK)) 151 .build(), 152 displayId); 153 } 154 } 155 sendHome(int displayId)156 void sendHome(int displayId) { 157 setFocusedDisplayId(displayId); 158 mRemoteIo.sendMessage( 159 RemoteEvent.newBuilder() 160 .setDisplayId(displayId) 161 .setHomeEvent(RemoteHomeEvent.newBuilder()) 162 .build()); 163 } 164 sendTouchEvent(InputDeviceType deviceType, MotionEvent event, int displayId)165 private void sendTouchEvent(InputDeviceType deviceType, MotionEvent event, int displayId) { 166 setFocusedDisplayId(displayId); 167 for (int pointerIndex = 0; pointerIndex < event.getPointerCount(); pointerIndex++) { 168 sendInputEvent( 169 RemoteInputEvent.newBuilder() 170 .setDeviceType(deviceType) 171 .setTimestampMs(event.getEventTime()) 172 .setTouchEvent( 173 RemoteMotionEvent.newBuilder() 174 .setPointerId(event.getPointerId(pointerIndex)) 175 .setAction(event.getActionMasked()) 176 .setX(event.getX(pointerIndex)) 177 .setY(event.getY(pointerIndex)) 178 .setPressure(event.getPressure(pointerIndex))) 179 .build(), 180 displayId); 181 } 182 } 183 sendKeyEvent(InputDeviceType deviceType, KeyEvent event, int displayId)184 private void sendKeyEvent(InputDeviceType deviceType, KeyEvent event, int displayId) { 185 sendInputEvent( 186 RemoteInputEvent.newBuilder() 187 .setDeviceType(deviceType) 188 .setTimestampMs(event.getEventTime()) 189 .setKeyEvent( 190 RemoteKeyEvent.newBuilder() 191 .setAction(event.getAction()) 192 .setKeyCode(event.getKeyCode()) 193 .build()) 194 .build(), 195 displayId); 196 } 197 sendMouseEvent(MotionEvent event, int displayId)198 private void sendMouseEvent(MotionEvent event, int displayId) { 199 switch (event.getAction()) { 200 case MotionEvent.ACTION_BUTTON_PRESS: 201 case MotionEvent.ACTION_BUTTON_RELEASE: 202 RemoteInputEvent buttonEvent = 203 RemoteInputEvent.newBuilder() 204 .setTimestampMs(System.currentTimeMillis()) 205 .setDeviceType(InputDeviceType.DEVICE_TYPE_MOUSE) 206 .setMouseButtonEvent( 207 RemoteKeyEvent.newBuilder() 208 .setAction(event.getAction()) 209 .setKeyCode(event.getActionButton()) 210 .build()) 211 .build(); 212 sendInputEvent(buttonEvent, displayId); 213 break; 214 case MotionEvent.ACTION_HOVER_ENTER: 215 case MotionEvent.ACTION_HOVER_EXIT: 216 case MotionEvent.ACTION_HOVER_MOVE: 217 setFocusedDisplayId(displayId); 218 RemoteInputEvent relativeEvent = 219 RemoteInputEvent.newBuilder() 220 .setTimestampMs(System.currentTimeMillis()) 221 .setDeviceType(InputDeviceType.DEVICE_TYPE_MOUSE) 222 .setMouseRelativeEvent( 223 RemoteMotionEvent.newBuilder() 224 .setX(event.getX()) 225 .setY(event.getY()) 226 .build()) 227 .build(); 228 sendInputEvent(relativeEvent, displayId); 229 break; 230 case MotionEvent.ACTION_SCROLL: 231 float scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 232 float scrollY = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 233 RemoteInputEvent scrollEvent = 234 RemoteInputEvent.newBuilder() 235 .setTimestampMs(System.currentTimeMillis()) 236 .setDeviceType(InputDeviceType.DEVICE_TYPE_MOUSE) 237 .setMouseScrollEvent( 238 RemoteMotionEvent.newBuilder() 239 .setX(clampMouseScroll(scrollX)) 240 .setY(clampMouseScroll(scrollY)) 241 .build()) 242 .build(); 243 sendInputEvent(scrollEvent, displayId); 244 break; 245 } 246 } 247 sendInputEvent(RemoteInputEvent inputEvent, int displayId)248 private void sendInputEvent(RemoteInputEvent inputEvent, int displayId) { 249 mRemoteIo.sendMessage( 250 RemoteEvent.newBuilder().setDisplayId(displayId).setInputEvent(inputEvent).build()); 251 } 252 clampMouseScroll(float val)253 private static float clampMouseScroll(float val) { 254 return Math.max(Math.min(val, 1f), -1f); 255 } 256 updateFocusedDisplayId()257 private int updateFocusedDisplayId() { 258 synchronized (mLock) { 259 if (mFocusableDisplays.contains(mFocusedDisplayId)) { 260 return mFocusedDisplayId; 261 } 262 return Iterables.getFirst(mFocusableDisplays, Display.INVALID_DISPLAY); 263 } 264 } 265 setFocusedDisplayId(int displayId)266 void setFocusedDisplayId(int displayId) { 267 List<FocusListener> listenersToNotify = Collections.emptyList(); 268 boolean focusedDisplayChanged = false; 269 synchronized (mLock) { 270 if (displayId != mFocusedDisplayId) { 271 mFocusedDisplayId = displayId; 272 listenersToNotify = new ArrayList<>(mFocusListeners); 273 focusedDisplayChanged = true; 274 } 275 } 276 if (focusedDisplayChanged) { 277 mRemoteIo.sendMessage(RemoteEvent.newBuilder() 278 .setDisplayId(displayId) 279 .setDisplayChangeEvent(DisplayChangeEvent.newBuilder().setFocused(true)) 280 .build()); 281 } 282 for (FocusListener focusListener : listenersToNotify) { 283 focusListener.onFocusChange(displayId); 284 } 285 } 286 } 287