1 /*
2  * Copyright (C) 2016 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.server.tv;
18 
19 import android.os.IBinder;
20 
21 import dalvik.system.CloseGuard;
22 
23 import java.io.IOException;
24 
25 /**
26  * Sends the input event to the linux driver.
27  */
28 public final class UinputBridge {
29     private final CloseGuard mCloseGuard = CloseGuard.get();
30     private long mPtr;
31     private IBinder mToken;
32 
nativeOpen(String name, String uniqueId, int width, int height, int maxPointers)33     private static native long nativeOpen(String name, String uniqueId, int width, int height,
34                                           int maxPointers);
nativeClose(long ptr)35     private static native void nativeClose(long ptr);
nativeClear(long ptr)36     private static native void nativeClear(long ptr);
nativeSendKey(long ptr, int keyCode, boolean down)37     private static native void nativeSendKey(long ptr, int keyCode, boolean down);
nativeSendPointerDown(long ptr, int pointerId, int x, int y)38     private static native void nativeSendPointerDown(long ptr, int pointerId, int x, int y);
nativeSendPointerUp(long ptr, int pointerId)39     private static native void nativeSendPointerUp(long ptr, int pointerId);
nativeSendPointerSync(long ptr)40     private static native void nativeSendPointerSync(long ptr);
41 
42     /** Opens a gamepad - will support gamepad key and axis sending */
nativeGamepadOpen(String name, String uniqueId)43     private static native long nativeGamepadOpen(String name, String uniqueId);
44 
45     /**
46      * Marks the specified key up/down for a gamepad.
47      *
48      *  @param keyCode - a code like BUTTON_MODE, BUTTON_A, BUTTON_B, ...
49      */
nativeSendGamepadKey(long ptr, int keyCode, boolean down)50     private static native void nativeSendGamepadKey(long ptr, int keyCode, boolean down);
51 
52     /**
53      * Send an axis value.
54      *
55      * Available axes are:
56      *  <li> Left joystick: AXIS_X, AXIS_Y
57      *  <li> Right joystick: AXIS_Z, AXIS_RZ
58      *  <li> Analog triggers: AXIS_LTRIGGER, AXIS_RTRIGGER
59      *  <li> DPad: AXIS_HAT_X, AXIS_HAT_Y
60      *
61      * @param axis is a MotionEvent.AXIS_* value.
62      * @param value is a value between -1 and 1 (inclusive)
63      *
64      */
nativeSendGamepadAxisValue(long ptr, int axis, float value)65     private static native void nativeSendGamepadAxisValue(long ptr, int axis, float value);
66 
UinputBridge(IBinder token, String name, int width, int height, int maxPointers)67     public UinputBridge(IBinder token, String name, int width, int height, int maxPointers)
68                         throws IOException {
69         if (width < 1 || height < 1) {
70             throw new IllegalArgumentException("Touchpad must be at least 1x1.");
71         }
72         if (maxPointers < 1 || maxPointers > 32) {
73             throw new IllegalArgumentException("Touchpad must support between 1 and 32 pointers.");
74         }
75         if (token == null) {
76             throw new IllegalArgumentException("Token cannot be null");
77         }
78         mPtr = nativeOpen(name, token.toString(), width, height, maxPointers);
79         if (mPtr == 0) {
80             throw new IOException("Could not open uinput device " + name);
81         }
82         mToken = token;
83         mCloseGuard.open("close");
84     }
85 
86     /** Constructor used by static factory methods */
UinputBridge(IBinder token, long ptr)87     private UinputBridge(IBinder token, long ptr) {
88         mPtr = ptr;
89         mToken = token;
90         mCloseGuard.open("close");
91     }
92 
93     /** Opens a UinputBridge that supports gamepad buttons and axes. */
openGamepad(IBinder token, String name)94     public static UinputBridge openGamepad(IBinder token, String name)
95             throws IOException {
96         if (token == null) {
97             throw new IllegalArgumentException("Token cannot be null");
98         }
99         long ptr = nativeGamepadOpen(name, token.toString());
100         if (ptr == 0) {
101             throw new IOException("Could not open uinput device " + name);
102         }
103 
104         return new UinputBridge(token, ptr);
105     }
106 
107     @Override
finalize()108     protected void finalize() throws Throwable {
109         try {
110             mCloseGuard.warnIfOpen();
111             close(mToken);
112         } finally {
113             mToken = null;
114             super.finalize();
115         }
116     }
117 
close(IBinder token)118     public void close(IBinder token) {
119         if (isTokenValid(token)) {
120             if (mPtr != 0) {
121                 clear(token);
122                 nativeClose(mPtr);
123 
124                 mPtr = 0;
125                 mCloseGuard.close();
126             }
127         }
128     }
129 
getToken()130     public IBinder getToken() {
131         return mToken;
132     }
133 
isTokenValid(IBinder token)134     protected boolean isTokenValid(IBinder token) {
135         return mToken.equals(token);
136     }
137 
sendKeyDown(IBinder token, int keyCode)138     public void sendKeyDown(IBinder token, int keyCode) {
139         if (isTokenValid(token)) {
140             nativeSendKey(mPtr, keyCode, true /*down*/);
141         }
142     }
143 
sendKeyUp(IBinder token, int keyCode)144     public void sendKeyUp(IBinder token, int keyCode) {
145         if (isTokenValid(token)) {
146             nativeSendKey(mPtr, keyCode, false /*down*/);
147         }
148     }
149 
sendPointerDown(IBinder token, int pointerId, int x, int y)150     public void sendPointerDown(IBinder token, int pointerId, int x, int y) {
151         if (isTokenValid(token)) {
152             nativeSendPointerDown(mPtr, pointerId, x, y);
153         }
154     }
155 
sendPointerUp(IBinder token, int pointerId)156     public void sendPointerUp(IBinder token, int pointerId) {
157         if (isTokenValid(token)) {
158             nativeSendPointerUp(mPtr, pointerId);
159         }
160     }
161 
sendPointerSync(IBinder token)162     public void sendPointerSync(IBinder token) {
163         if (isTokenValid(token)) {
164             nativeSendPointerSync(mPtr);
165         }
166     }
167 
168     /** Send a gamepad key
169      *  @param keyIndex - the index of the w3-spec key
170      *  @param down - is the key pressed ?
171      */
sendGamepadKey(IBinder token, int keyCode, boolean down)172     public void sendGamepadKey(IBinder token, int keyCode, boolean down) {
173         if (isTokenValid(token)) {
174             nativeSendGamepadKey(mPtr, keyCode, down);
175         }
176     }
177 
178     /**
179      * Send a gamepad axis value.
180      *
181      * @param axis is the axis code (MotionEvent.AXIS_*)
182      * @param value is the value to set for that axis in [-1, 1]
183      */
sendGamepadAxisValue(IBinder token, int axis, float value)184     public void sendGamepadAxisValue(IBinder token, int axis, float value) {
185         if (isTokenValid(token)) {
186             nativeSendGamepadAxisValue(mPtr, axis, value);
187         }
188     }
189 
clear(IBinder token)190     public void clear(IBinder token) {
191         if (isTokenValid(token)) {
192             nativeClear(mPtr);
193         }
194     }
195 
196 }
197