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