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