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.media.tv.remoteprovider;
18 
19 import android.annotation.FloatRange;
20 import android.annotation.NonNull;
21 import android.annotation.SuppressAutoDoc;
22 import android.content.Context;
23 import android.media.tv.ITvRemoteProvider;
24 import android.media.tv.ITvRemoteServiceInput;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.support.annotation.IntDef;
28 import android.util.Log;
29 import android.view.KeyEvent;
30 import android.view.MotionEvent;
31 
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.LinkedList;
35 import java.util.Objects;
36 
37 /**
38  * Base class for emote providers implemented in unbundled service.
39  * <p/>
40  * This object is not thread safe.  It is only intended to be accessed on the
41  * {@link Context#getMainLooper main looper thread} of an application.
42  * The callback {@link #onInputBridgeConnected()} may be called from a different thread.
43  * </p><p>
44  * IMPORTANT: This class is effectively a system API for unbundled emote service, and
45  * must remain API stable. See README.txt in the root of this package for more information.
46  * </p>
47  */
48 
49 
50 public abstract class TvRemoteProvider {
51 
52     /** @hide */
53     @IntDef({
54          KeyEvent.KEYCODE_BUTTON_A,
55          KeyEvent.KEYCODE_BUTTON_B,
56          KeyEvent.KEYCODE_BUTTON_X,
57          KeyEvent.KEYCODE_BUTTON_Y,
58          KeyEvent.KEYCODE_BUTTON_L1,
59          KeyEvent.KEYCODE_BUTTON_L2,
60          KeyEvent.KEYCODE_BUTTON_R1,
61          KeyEvent.KEYCODE_BUTTON_R2,
62          KeyEvent.KEYCODE_BUTTON_SELECT,
63          KeyEvent.KEYCODE_BUTTON_START,
64          KeyEvent.KEYCODE_BUTTON_MODE,
65          KeyEvent.KEYCODE_BUTTON_THUMBL,
66          KeyEvent.KEYCODE_BUTTON_THUMBR,
67          KeyEvent.KEYCODE_DPAD_UP,
68          KeyEvent.KEYCODE_DPAD_DOWN,
69          KeyEvent.KEYCODE_DPAD_LEFT,
70          KeyEvent.KEYCODE_DPAD_RIGHT,
71          KeyEvent.KEYCODE_BUTTON_1,
72          KeyEvent.KEYCODE_BUTTON_2,
73          KeyEvent.KEYCODE_BUTTON_3,
74          KeyEvent.KEYCODE_BUTTON_4,
75          KeyEvent.KEYCODE_BUTTON_5,
76          KeyEvent.KEYCODE_BUTTON_6,
77          KeyEvent.KEYCODE_BUTTON_7,
78          KeyEvent.KEYCODE_BUTTON_8,
79          KeyEvent.KEYCODE_BUTTON_9,
80          KeyEvent.KEYCODE_BUTTON_10,
81          KeyEvent.KEYCODE_BUTTON_11,
82          KeyEvent.KEYCODE_BUTTON_12,
83          KeyEvent.KEYCODE_BUTTON_13,
84          KeyEvent.KEYCODE_BUTTON_14,
85          KeyEvent.KEYCODE_BUTTON_15,
86          KeyEvent.KEYCODE_BUTTON_16,
87          KeyEvent.KEYCODE_ASSIST,
88          KeyEvent.KEYCODE_VOICE_ASSIST,
89     })
90     @Retention(RetentionPolicy.SOURCE)
91     public @interface GamepadKeyCode {
92     }
93 
94     /** @hide */
95     @IntDef({
96          MotionEvent.AXIS_X,
97          MotionEvent.AXIS_Y,
98          MotionEvent.AXIS_Z,
99          MotionEvent.AXIS_RZ,
100          MotionEvent.AXIS_LTRIGGER,
101          MotionEvent.AXIS_RTRIGGER,
102          MotionEvent.AXIS_HAT_X,
103          MotionEvent.AXIS_HAT_Y,
104     })
105     @Retention(RetentionPolicy.SOURCE)
106     public @interface GamepadAxis {
107     }
108 
109     /**
110      * The {@link Intent} that must be declared as handled by the service.
111      * The service must also require the {@link android.Manifest.permission#BIND_TV_REMOTE_SERVICE}
112      * permission so that other applications cannot abuse it.
113      */
114     public static final String SERVICE_INTERFACE =
115             "com.android.media.tv.remoteprovider.TvRemoteProvider";
116 
117     private static final String TAG = "TvRemoteProvider";
118     private static final boolean DEBUG_KEYS = false;
119     private final Context mContext;
120     private final ProviderStub mStub;
121     private final LinkedList<Runnable> mOpenBridgeRunnables;
122     private ITvRemoteServiceInput mRemoteServiceInput;
123 
124     /**
125      * Creates a provider for an unbundled emote controller
126      * service allowing it to interface with the tv remote controller
127      * system service.
128      *
129      * @param context The application context for the remote provider.
130      */
TvRemoteProvider(Context context)131     public TvRemoteProvider(Context context) {
132         mContext = context.getApplicationContext();
133         mStub = new ProviderStub();
134         mOpenBridgeRunnables = new LinkedList<Runnable>();
135     }
136 
137     /**
138      * Gets the context of the remote service provider.
139      */
getContext()140     public final Context getContext() {
141         return mContext;
142     }
143 
144     /**
145      * Gets the Binder associated with the provider.
146      * <p>
147      * This is intended to be used for the onBind() method of a service that implements
148      * a remote provider service.
149      * </p>
150      *
151      * @return The IBinder instance associated with the provider.
152      */
getBinder()153     public IBinder getBinder() {
154         return mStub;
155     }
156 
157     /**
158      * Information about the InputBridge connected status.
159      *
160      * @param token Identifier for the connection. Null, if failed.
161      */
onInputBridgeConnected(@onNull IBinder token)162     public void onInputBridgeConnected(@NonNull IBinder token) {
163     }
164 
165     /**
166      * Set a sink for sending events to framework service.
167      *
168      * @param tvServiceInput sink defined in framework service
169      */
setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput)170     private void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) {
171         synchronized (mOpenBridgeRunnables) {
172             mRemoteServiceInput = tvServiceInput;
173         }
174         mOpenBridgeRunnables.forEach(Runnable::run);
175         mOpenBridgeRunnables.clear();
176     }
177 
178     /**
179      * openRemoteInputBridge : Open an input bridge for a particular device.
180      * Clients should pass in a token that can be used to match this request with a token that
181      * will be returned by {@link TvRemoteProvider#onInputBridgeConnected(IBinder token)}
182      * <p>
183      * The token should be used for subsequent calls.
184      * </p>
185      *
186      * @param name        Device name
187      * @param token       Identifier for this connection
188      * @param width       Width of the device's virtual touchpad
189      * @param height      Height of the device's virtual touchpad
190      * @param maxPointers Maximum supported pointers
191      * @throws RuntimeException
192      */
openRemoteInputBridge( @onNull IBinder token, @NonNull String name, int width, int height, int maxPointers)193     public void openRemoteInputBridge(
194             @NonNull IBinder token, @NonNull String name, int width, int height, int maxPointers)
195             throws RuntimeException {
196         final IBinder finalToken = Objects.requireNonNull(token);
197         final String finalName = Objects.requireNonNull(name);
198 
199         synchronized (mOpenBridgeRunnables) {
200             if (mRemoteServiceInput == null) {
201                 Log.d(TAG, "Delaying openRemoteInputBridge() for " + finalName);
202 
203                 mOpenBridgeRunnables.add(() -> {
204                     try {
205                         mRemoteServiceInput.openInputBridge(
206                                 finalToken, finalName, width, height, maxPointers);
207                         Log.d(TAG, "Delayed openRemoteInputBridge() for " + finalName
208                                 + ": success");
209                     } catch (RemoteException re) {
210                         Log.e(TAG, "Delayed openRemoteInputBridge() for " + finalName
211                                 + ": failure", re);
212                     }
213                 });
214                 return;
215             }
216         }
217         try {
218             mRemoteServiceInput.openInputBridge(finalToken, finalName, width, height, maxPointers);
219             Log.d(TAG, "openRemoteInputBridge() for " + finalName + ": success");
220         } catch (RemoteException re) {
221             throw re.rethrowFromSystemServer();
222         }
223     }
224 
225     /**
226      * Opens an input bridge as a gamepad device.
227      * Clients should pass in a token that can be used to match this request with a token that
228      * will be returned by {@link TvRemoteProvider#onInputBridgeConnected(IBinder token)}
229      * <p>
230      * The token should be used for subsequent calls.
231      * </p>
232      *
233      * @param token       Identifier for this connection
234      * @param name        Device name
235      * @throws RuntimeException
236      */
openGamepadBridge(@onNull IBinder token, @NonNull String name)237     public void openGamepadBridge(@NonNull IBinder token, @NonNull  String name)
238             throws RuntimeException {
239         final IBinder finalToken = Objects.requireNonNull(token);
240         final String finalName = Objects.requireNonNull(name);
241         synchronized (mOpenBridgeRunnables) {
242             if (mRemoteServiceInput == null) {
243                 Log.d(TAG, "Delaying openGamepadBridge() for " + finalName);
244 
245                 mOpenBridgeRunnables.add(() -> {
246                     try {
247                         mRemoteServiceInput.openGamepadBridge(finalToken, finalName);
248                         Log.d(TAG, "Delayed openGamepadBridge() for " + finalName + ": success");
249                     } catch (RemoteException re) {
250                         Log.e(TAG, "Delayed openGamepadBridge() for " + finalName + ": failure",
251                                 re);
252                     }
253                 });
254                 return;
255             }
256         }
257         try {
258             mRemoteServiceInput.openGamepadBridge(token, finalName);
259             Log.d(TAG, "openGamepadBridge() for " + finalName + ": success");
260         } catch (RemoteException re) {
261             throw re.rethrowFromSystemServer();
262         }
263     }
264 
265     /**
266      * closeInputBridge : Close input bridge for a device
267      *
268      * @param token identifier for this connection
269      * @throws RuntimeException
270      */
closeInputBridge(@onNull IBinder token)271     public void closeInputBridge(@NonNull IBinder token) throws RuntimeException {
272         Objects.requireNonNull(token);
273         try {
274             mRemoteServiceInput.closeInputBridge(token);
275         } catch (RemoteException re) {
276             throw re.rethrowFromSystemServer();
277         }
278     }
279 
280     /**
281      * clearInputBridge : Clear out any existing key or pointer events in queue for this device by
282      *                    dropping them on the floor and sending an UP to all keys and pointer
283      *                    slots.
284      *
285      * @param token identifier for this connection
286      * @throws RuntimeException
287      */
clearInputBridge(@onNull IBinder token)288     public void clearInputBridge(@NonNull IBinder token) throws RuntimeException {
289         Objects.requireNonNull(token);
290         if (DEBUG_KEYS) Log.d(TAG, "clearInputBridge() token " + token);
291         try {
292             mRemoteServiceInput.clearInputBridge(token);
293         } catch (RemoteException re) {
294             throw re.rethrowFromSystemServer();
295         }
296     }
297 
298     /**
299      * sendTimestamp : Send a timestamp for a set of pointer events
300      *
301      * @param token     identifier for the device
302      * @param timestamp Timestamp to be used in
303      *                  {@link android.os.SystemClock#uptimeMillis} time base
304      * @throws RuntimeException
305      */
sendTimestamp(@onNull IBinder token, long timestamp)306     public void sendTimestamp(@NonNull IBinder token, long timestamp) throws RuntimeException {
307         Objects.requireNonNull(token);
308         if (DEBUG_KEYS) Log.d(TAG, "sendTimestamp() token: " + token +
309                 ", timestamp: " + timestamp);
310         try {
311             mRemoteServiceInput.sendTimestamp(token, timestamp);
312         } catch (RemoteException re) {
313             throw re.rethrowFromSystemServer();
314         }
315     }
316 
317     /**
318      * sendKeyUp : Send key up event for a device
319      *
320      * @param token   identifier for this connection
321      * @param keyCode Key code to be sent
322      * @throws RuntimeException
323      */
sendKeyUp(@onNull IBinder token, int keyCode)324     public void sendKeyUp(@NonNull IBinder token, int keyCode) throws RuntimeException {
325         Objects.requireNonNull(token);
326         if (DEBUG_KEYS) Log.d(TAG, "sendKeyUp() token: " + token + ", keyCode: " + keyCode);
327         try {
328             mRemoteServiceInput.sendKeyUp(token, keyCode);
329         } catch (RemoteException re) {
330             throw re.rethrowFromSystemServer();
331         }
332     }
333 
334     /**
335      * sendKeyDown : Send key down event for a device
336      *
337      * @param token   identifier for this connection
338      * @param keyCode Key code to be sent
339      * @throws RuntimeException
340      */
sendKeyDown(@onNull IBinder token, int keyCode)341     public void sendKeyDown(@NonNull IBinder token, int keyCode) throws RuntimeException {
342         Objects.requireNonNull(token);
343         if (DEBUG_KEYS) Log.d(TAG, "sendKeyDown() token: " + token +
344                 ", keyCode: " + keyCode);
345         try {
346             mRemoteServiceInput.sendKeyDown(token, keyCode);
347         } catch (RemoteException re) {
348             throw re.rethrowFromSystemServer();
349         }
350     }
351 
352     /**
353      * sendPointerUp : Send pointer up event for a device
354      *
355      * @param token     identifier for the device
356      * @param pointerId Pointer id to be used. Value may be from 0
357      *                  to {@link MotionEvent#getPointerCount()} -1
358      * @throws RuntimeException
359      */
sendPointerUp(@onNull IBinder token, int pointerId)360     public void sendPointerUp(@NonNull IBinder token, int pointerId) throws RuntimeException {
361         Objects.requireNonNull(token);
362         if (DEBUG_KEYS) Log.d(TAG, "sendPointerUp() token: " + token +
363                 ", pointerId: " + pointerId);
364         try {
365             mRemoteServiceInput.sendPointerUp(token, pointerId);
366         } catch (RemoteException re) {
367             throw re.rethrowFromSystemServer();
368         }
369     }
370 
371     /**
372      * sendPointerDown : Send pointer down event for a device
373      *
374      * @param token     identifier for the device
375      * @param pointerId Pointer id to be used. Value may be from 0
376      *                  to {@link MotionEvent#getPointerCount()} -1
377      * @param x         X co-ordinates in display pixels
378      * @param y         Y co-ordinates in display pixels
379      * @throws RuntimeException
380      */
sendPointerDown(@onNull IBinder token, int pointerId, int x, int y)381     public void sendPointerDown(@NonNull IBinder token, int pointerId, int x, int y)
382             throws RuntimeException {
383         Objects.requireNonNull(token);
384         if (DEBUG_KEYS) Log.d(TAG, "sendPointerDown() token: " + token +
385                 ", pointerId: " + pointerId);
386         try {
387             mRemoteServiceInput.sendPointerDown(token, pointerId, x, y);
388         } catch (RemoteException re) {
389             throw re.rethrowFromSystemServer();
390         }
391     }
392 
393     /**
394      * sendPointerSync : Send pointer sync event for a device
395      *
396      * @param token identifier for the device
397      * @throws RuntimeException
398      */
sendPointerSync(@onNull IBinder token)399     public void sendPointerSync(@NonNull IBinder token) throws RuntimeException {
400         Objects.requireNonNull(token);
401         if (DEBUG_KEYS) Log.d(TAG, "sendPointerSync() token: " + token);
402         try {
403             mRemoteServiceInput.sendPointerSync(token);
404         } catch (RemoteException re) {
405             throw re.rethrowFromSystemServer();
406         }
407     }
408 
409     /**
410      * Send a notification that a gamepad key was pressed.
411      *
412      * Supported buttons are:
413      * <ul>
414      *   <li> Right-side buttons: BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y
415      *   <li> Digital Triggers and bumpers: BUTTON_L1, BUTTON_R1, BUTTON_L2, BUTTON_R2
416      *   <li> Thumb buttons: BUTTON_THUMBL, BUTTON_THUMBR
417      *   <li> DPad buttons: DPAD_UP, DPAD_DOWN, DPAD_LEFT, DPAD_RIGHT
418      *   <li> Gamepad buttons: BUTTON_SELECT, BUTTON_START, BUTTON_MODE
419      *   <li> Generic buttons: BUTTON_1, BUTTON_2, ...., BUTTON16
420      *   <li> Assistant: ASSIST, VOICE_ASSIST
421      * </ul>
422      *
423      * @param token   identifier for the device. This value must never be null.
424      * @param keyCode the gamepad key that was pressed (like BUTTON_A)
425      *
426      */
427     @SuppressAutoDoc
sendGamepadKeyDown(@onNull IBinder token, @GamepadKeyCode int keyCode)428     public void sendGamepadKeyDown(@NonNull IBinder token, @GamepadKeyCode int keyCode)
429             throws RuntimeException {
430         Objects.requireNonNull(token);
431         if (DEBUG_KEYS) {
432             Log.d(TAG, "sendGamepadKeyDown() token: " + token);
433         }
434 
435         try {
436             mRemoteServiceInput.sendGamepadKeyDown(token, keyCode);
437         } catch (RemoteException re) {
438             throw re.rethrowFromSystemServer();
439         }
440     }
441 
442     /**
443      * Send a notification that a gamepad key was released.
444      *
445      * @see sendGamepadKeyDown for supported key codes.
446      *
447      * @param token identifier for the device. This value mus never be null.
448      * @param keyCode the gamepad key that was pressed
449      */
450     @SuppressAutoDoc
sendGamepadKeyUp(@onNull IBinder token, @GamepadKeyCode int keyCode)451     public void sendGamepadKeyUp(@NonNull IBinder token, @GamepadKeyCode int keyCode)
452             throws RuntimeException {
453         Objects.requireNonNull(token);
454         if (DEBUG_KEYS) {
455             Log.d(TAG, "sendGamepadKeyUp() token: " + token);
456         }
457 
458         try {
459             mRemoteServiceInput.sendGamepadKeyUp(token, keyCode);
460         } catch (RemoteException re) {
461             throw re.rethrowFromSystemServer();
462         }
463     }
464 
465     /**
466      * Send a gamepad axis value.
467      *
468      * Supported axes:
469      *  <li> Left Joystick: AXIS_X, AXIS_Y
470      *  <li> Right Joystick: AXIS_Z, AXIS_RZ
471      *  <li> Triggers: AXIS_LTRIGGER, AXIS_RTRIGGER
472      *  <li> DPad: AXIS_HAT_X, AXIS_HAT_Y
473      *
474      * For non-trigger axes, the range of acceptable values is [-1, 1]. The trigger axes support
475      * values [0, 1].
476      *
477      * @param token identifier for the device. This value must never be null.
478      * @param axis  MotionEvent axis
479      * @param value the value to send
480      */
481     @SuppressAutoDoc
sendGamepadAxisValue( @onNull IBinder token, @GamepadAxis int axis, @FloatRange(from = -1.0f, to = 1.0f) float value)482     public void sendGamepadAxisValue(
483             @NonNull IBinder token, @GamepadAxis int axis,
484             @FloatRange(from = -1.0f, to = 1.0f) float value) throws RuntimeException {
485         Objects.requireNonNull(token);
486         if (DEBUG_KEYS) {
487             Log.d(TAG, "sendGamepadAxisValue() token: " + token);
488         }
489 
490         try {
491             mRemoteServiceInput.sendGamepadAxisValue(token, axis, value);
492         } catch (RemoteException re) {
493             throw re.rethrowFromSystemServer();
494         }
495     }
496 
497     private final class ProviderStub extends ITvRemoteProvider.Stub {
498         @Override
setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput)499         public void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) {
500             TvRemoteProvider.this.setRemoteServiceInputSink(tvServiceInput);
501         }
502 
503         @Override
onInputBridgeConnected(IBinder token)504         public void onInputBridgeConnected(IBinder token) {
505             TvRemoteProvider.this.onInputBridgeConnected(token);
506         }
507     }
508 }
509