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