TvInputService.java revision 969167dc05a6485a32d160895871cff46fd81884
1/* 2 * Copyright (C) 2014 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 17package android.media.tv; 18 19import android.annotation.SuppressLint; 20import android.app.Service; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.Intent; 24import android.graphics.PixelFormat; 25import android.graphics.Rect; 26import android.net.Uri; 27import android.os.Bundle; 28import android.os.Handler; 29import android.os.IBinder; 30import android.os.Message; 31import android.os.RemoteCallbackList; 32import android.os.RemoteException; 33import android.util.Log; 34import android.view.Gravity; 35import android.view.InputChannel; 36import android.view.InputDevice; 37import android.view.InputEvent; 38import android.view.InputEventReceiver; 39import android.view.KeyEvent; 40import android.view.MotionEvent; 41import android.view.Surface; 42import android.view.View; 43import android.view.WindowManager; 44import android.view.accessibility.CaptioningManager; 45 46import com.android.internal.annotations.VisibleForTesting; 47import com.android.internal.os.SomeArgs; 48 49import java.util.List; 50 51/** 52 * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which 53 * provides pass-through video or broadcast TV programs. 54 * <p> 55 * Applications will not normally use this service themselves, instead relying on the standard 56 * interaction provided by {@link TvView}. Those implementing TV input services should normally do 57 * so by deriving from this class and providing their own session implementation based on 58 * {@link TvInputService.Session}. All TV input services must require that clients hold the 59 * {@link android.Manifest.permission#BIND_TV_INPUT} in order to interact with the service; if this 60 * permission is not specified in the manifest, the system will refuse to bind to that TV input 61 * service. 62 * </p> 63 */ 64public abstract class TvInputService extends Service { 65 // STOPSHIP: Turn debugging off. 66 private static final boolean DEBUG = true; 67 private static final String TAG = "TvInputService"; 68 69 /** 70 * This is the interface name that a service implementing a TV input should say that it support 71 * -- that is, this is the action it uses for its intent filter. To be supported, the service 72 * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that 73 * other applications cannot abuse it. 74 */ 75 public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService"; 76 77 /** 78 * Name under which a TvInputService component publishes information about itself. 79 * This meta-data must reference an XML resource containing an 80 * <code><{@link android.R.styleable#TvInputService tv-input}></code> 81 * tag. 82 */ 83 public static final String SERVICE_META_DATA = "android.media.tv.input"; 84 85 private final Handler mHandler = new ServiceHandler(); 86 private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks = 87 new RemoteCallbackList<ITvInputServiceCallback>(); 88 89 @Override 90 public final IBinder onBind(Intent intent) { 91 return new ITvInputService.Stub() { 92 @Override 93 public void registerCallback(ITvInputServiceCallback cb) { 94 if (cb != null) { 95 mCallbacks.register(cb); 96 } 97 } 98 99 @Override 100 public void unregisterCallback(ITvInputServiceCallback cb) { 101 if (cb != null) { 102 mCallbacks.unregister(cb); 103 } 104 } 105 106 @Override 107 public void createSession(InputChannel channel, ITvInputSessionCallback cb) { 108 if (channel == null) { 109 Log.w(TAG, "Creating session without input channel"); 110 } 111 if (cb == null) { 112 return; 113 } 114 SomeArgs args = SomeArgs.obtain(); 115 args.arg1 = channel; 116 args.arg2 = cb; 117 mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget(); 118 } 119 }; 120 } 121 122 /** 123 * Get the number of callbacks that are registered. 124 * 125 * @hide 126 */ 127 @VisibleForTesting 128 public final int getRegisteredCallbackCount() { 129 return mCallbacks.getRegisteredCallbackCount(); 130 } 131 132 /** 133 * Returns a concrete implementation of {@link Session}. 134 * <p> 135 * May return {@code null} if this TV input service fails to create a session for some reason. 136 * </p> 137 */ 138 public abstract Session onCreateSession(); 139 140 /** 141 * Base class for derived classes to implement to provide a TV input session. 142 */ 143 public abstract class Session implements KeyEvent.Callback { 144 private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); 145 private final WindowManager mWindowManager; 146 private WindowManager.LayoutParams mWindowParams; 147 private Surface mSurface; 148 private View mOverlayView; 149 private boolean mOverlayViewEnabled; 150 private IBinder mWindowToken; 151 private Rect mOverlayFrame; 152 private ITvInputSessionCallback mSessionCallback; 153 154 public Session() { 155 mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); 156 } 157 158 /** 159 * Enables or disables the overlay view. By default, the overlay view is disabled. Must be 160 * called explicitly after the session is created to enable the overlay view. 161 * 162 * @param enable {@code true} if you want to enable the overlay view. {@code false} 163 * otherwise. 164 */ 165 public void setOverlayViewEnabled(final boolean enable) { 166 mHandler.post(new Runnable() { 167 @Override 168 public void run() { 169 if (enable == mOverlayViewEnabled) { 170 return; 171 } 172 mOverlayViewEnabled = enable; 173 if (enable) { 174 if (mWindowToken != null) { 175 createOverlayView(mWindowToken, mOverlayFrame); 176 } 177 } else { 178 removeOverlayView(false); 179 } 180 } 181 }); 182 } 183 184 /** 185 * Dispatches an event to the application using this session. 186 * 187 * @param eventType The type of the event. 188 * @param eventArgs Optional arguments of the event. 189 * @hide 190 */ 191 public void dispatchSessionEvent(final String eventType, final Bundle eventArgs) { 192 if (eventType == null) { 193 throw new IllegalArgumentException("eventType should not be null."); 194 } 195 mHandler.post(new Runnable() { 196 @Override 197 public void run() { 198 try { 199 if (DEBUG) Log.d(TAG, "dispatchSessionEvent(" + eventType + ")"); 200 mSessionCallback.onSessionEvent(eventType, eventArgs); 201 } catch (RemoteException e) { 202 Log.w(TAG, "error in sending event (event=" + eventType + ")"); 203 } 204 } 205 }); 206 } 207 208 /** 209 * Notifies the channel of the session is retuned by TV input. 210 * 211 * @param channelUri The URI of a channel. 212 */ 213 public void dispatchChannelRetuned(final Uri channelUri) { 214 mHandler.post(new Runnable() { 215 @Override 216 public void run() { 217 try { 218 if (DEBUG) Log.d(TAG, "dispatchChannelRetuned"); 219 mSessionCallback.onChannelRetuned(channelUri); 220 } catch (RemoteException e) { 221 Log.w(TAG, "error in dispatchChannelRetuned"); 222 } 223 } 224 }); 225 } 226 227 /** 228 * Sends the change on the track information. This is expected to be called whenever a 229 * track is added/removed and the metadata of a track is modified. 230 * 231 * @param tracks A list which includes track information. 232 */ 233 public void dispatchTrackInfoChanged(final List<TvTrackInfo> tracks) { 234 if (!TvTrackInfo.checkSanity(tracks)) { 235 throw new IllegalArgumentException( 236 "Two or more selected tracks for a track type."); 237 } 238 mHandler.post(new Runnable() { 239 @Override 240 public void run() { 241 try { 242 if (DEBUG) Log.d(TAG, "dispatchTrackInfoChanged"); 243 mSessionCallback.onTrackInfoChanged(tracks); 244 } catch (RemoteException e) { 245 Log.w(TAG, "error in dispatchTrackInfoChanged"); 246 } 247 } 248 }); 249 } 250 251 /** 252 * Informs the application that video is available and the playback of the TV stream has 253 * been started. 254 */ 255 public void dispatchVideoAvailable() { 256 mHandler.post(new Runnable() { 257 @Override 258 public void run() { 259 try { 260 if (DEBUG) Log.d(TAG, "dispatchVideoAvailable"); 261 mSessionCallback.onVideoAvailable(); 262 } catch (RemoteException e) { 263 Log.w(TAG, "error in dispatchVideoAvailable"); 264 } 265 } 266 }); 267 } 268 269 /** 270 * Informs the application that video is not available, so the TV input cannot continue 271 * playing the TV stream. 272 * 273 * @param reason The reason why the TV input stopped the playback: 274 * <ul> 275 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN} 276 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNE} 277 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL} 278 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING} 279 * </ul> 280 */ 281 public void dispatchVideoUnavailable(final int reason) { 282 if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN 283 && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNE 284 && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL 285 && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) { 286 throw new IllegalArgumentException("Unknown reason: " + reason); 287 } 288 mHandler.post(new Runnable() { 289 @Override 290 public void run() { 291 try { 292 if (DEBUG) Log.d(TAG, "dispatchVideoUnavailable"); 293 mSessionCallback.onVideoUnavailable(reason); 294 } catch (RemoteException e) { 295 Log.w(TAG, "error in dispatchVideoUnavailable"); 296 } 297 } 298 }); 299 } 300 301 /** 302 * Called when the session is released. 303 */ 304 public abstract void onRelease(); 305 306 /** 307 * Sets the {@link Surface} for the current input session on which the TV input renders 308 * video. 309 * 310 * @param surface {@link Surface} an application passes to this TV input session. 311 * @return {@code true} if the surface was set, {@code false} otherwise. 312 */ 313 public abstract boolean onSetSurface(Surface surface); 314 315 /** 316 * Sets the relative stream volume of the current TV input session to handle the change of 317 * audio focus by setting. 318 * 319 * @param volume Volume scale from 0.0 to 1.0. 320 */ 321 public abstract void onSetStreamVolume(float volume); 322 323 /** 324 * Tunes to a given channel. When the video is available, {@link #dispatchVideoAvailable()} 325 * should be called. Also, {@link #dispatchVideoUnavailable(int)} should be called when the 326 * TV input cannot continue playing the given channel. 327 * 328 * @param channelUri The URI of the channel. 329 * @return {@code true} the tuning was successful, {@code false} otherwise. 330 */ 331 public abstract boolean onTune(Uri channelUri); 332 333 /** 334 * Enables or disables the caption. 335 * <p> 336 * The locale for the user's preferred captioning language can be obtained by calling 337 * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}. 338 * 339 * @param enabled {@code true} to enable, {@code false} to disable. 340 * @see CaptioningManager 341 */ 342 public abstract void onSetCaptionEnabled(boolean enabled); 343 344 /** 345 * Selects a given track. 346 * <p> 347 * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the 348 * track selected previously should be unselected in the implementation of this method. 349 * Also, if the select operation was successful, the implementation should call 350 * {@link #dispatchTrackInfoChanged(List)} to report the updated track information. 351 * </p> 352 * 353 * @param track The track to be selected. 354 * @return {@code true} if the select operation was successful, {@code false} otherwise. 355 * @see #dispatchTrackInfoChanged 356 * @see TvTrackInfo#KEY_IS_SELECTED 357 */ 358 public boolean onSelectTrack(TvTrackInfo track) { 359 return false; 360 } 361 362 /** 363 * Unselects a given track. 364 * <p> 365 * If the unselect operation was successful, the implementation should call 366 * {@link #dispatchTrackInfoChanged(List)} to report the updated track information. 367 * </p> 368 * 369 * @param track The track to be unselected. 370 * @return {@code true} if the unselect operation was successful, {@code false} otherwise. 371 * @see #dispatchTrackInfoChanged 372 * @see TvTrackInfo#KEY_IS_SELECTED 373 */ 374 public boolean onUnselectTrack(TvTrackInfo track) { 375 return false; 376 } 377 378 /** 379 * Called when an application requests to create an overlay view. Each session 380 * implementation can override this method and return its own view. 381 * 382 * @return a view attached to the overlay window 383 */ 384 public View onCreateOverlayView() { 385 return null; 386 } 387 388 /** 389 * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent) 390 * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event). 391 * <p> 392 * Override this to intercept key down events before they are processed by the application. 393 * If you return true, the application will not process the event itself. If you return 394 * false, the normal application processing will occur as if the TV input had not seen the 395 * event at all. 396 * 397 * @param keyCode The value in event.getKeyCode(). 398 * @param event Description of the key event. 399 * @return If you handled the event, return {@code true}. If you want to allow the event to 400 * be handled by the next receiver, return {@code false}. 401 */ 402 @Override 403 public boolean onKeyDown(int keyCode, KeyEvent event) { 404 return false; 405 } 406 407 /** 408 * Default implementation of 409 * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent) 410 * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event). 411 * <p> 412 * Override this to intercept key long press events before they are processed by the 413 * application. If you return true, the application will not process the event itself. If 414 * you return false, the normal application processing will occur as if the TV input had not 415 * seen the event at all. 416 * 417 * @param keyCode The value in event.getKeyCode(). 418 * @param event Description of the key event. 419 * @return If you handled the event, return {@code true}. If you want to allow the event to 420 * be handled by the next receiver, return {@code false}. 421 */ 422 @Override 423 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 424 return false; 425 } 426 427 /** 428 * Default implementation of 429 * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) 430 * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event). 431 * <p> 432 * Override this to intercept special key multiple events before they are processed by the 433 * application. If you return true, the application will not itself process the event. If 434 * you return false, the normal application processing will occur as if the TV input had not 435 * seen the event at all. 436 * 437 * @param keyCode The value in event.getKeyCode(). 438 * @param count The number of times the action was made. 439 * @param event Description of the key event. 440 * @return If you handled the event, return {@code true}. If you want to allow the event to 441 * be handled by the next receiver, return {@code false}. 442 */ 443 @Override 444 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { 445 return false; 446 } 447 448 /** 449 * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent) 450 * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event). 451 * <p> 452 * Override this to intercept key up events before they are processed by the application. If 453 * you return true, the application will not itself process the event. If you return false, 454 * the normal application processing will occur as if the TV input had not seen the event at 455 * all. 456 * 457 * @param keyCode The value in event.getKeyCode(). 458 * @param event Description of the key event. 459 * @return If you handled the event, return {@code true}. If you want to allow the event to 460 * be handled by the next receiver, return {@code false}. 461 */ 462 @Override 463 public boolean onKeyUp(int keyCode, KeyEvent event) { 464 return false; 465 } 466 467 /** 468 * Implement this method to handle touch screen motion events on the current input session. 469 * 470 * @param event The motion event being received. 471 * @return If you handled the event, return {@code true}. If you want to allow the event to 472 * be handled by the next receiver, return {@code false}. 473 * @see View#onTouchEvent 474 */ 475 public boolean onTouchEvent(MotionEvent event) { 476 return false; 477 } 478 479 /** 480 * Implement this method to handle trackball events on the current input session. 481 * 482 * @param event The motion event being received. 483 * @return If you handled the event, return {@code true}. If you want to allow the event to 484 * be handled by the next receiver, return {@code false}. 485 * @see View#onTrackballEvent 486 */ 487 public boolean onTrackballEvent(MotionEvent event) { 488 return false; 489 } 490 491 /** 492 * Implement this method to handle generic motion events on the current input session. 493 * 494 * @param event The motion event being received. 495 * @return If you handled the event, return {@code true}. If you want to allow the event to 496 * be handled by the next receiver, return {@code false}. 497 * @see View#onGenericMotionEvent 498 */ 499 public boolean onGenericMotionEvent(MotionEvent event) { 500 return false; 501 } 502 503 /** 504 * This method is called when the application would like to stop using the current input 505 * session. 506 */ 507 void release() { 508 onRelease(); 509 if (mSurface != null) { 510 mSurface.release(); 511 mSurface = null; 512 } 513 removeOverlayView(true); 514 } 515 516 /** 517 * Calls {@link #onSetSurface}. 518 */ 519 void setSurface(Surface surface) { 520 onSetSurface(surface); 521 if (mSurface != null) { 522 mSurface.release(); 523 } 524 mSurface = surface; 525 // TODO: Handle failure. 526 } 527 528 /** 529 * Calls {@link #onSetStreamVolume}. 530 */ 531 void setVolume(float volume) { 532 onSetStreamVolume(volume); 533 } 534 535 /** 536 * Calls {@link #onTune}. 537 */ 538 void tune(Uri channelUri) { 539 onTune(channelUri); 540 // TODO: Handle failure. 541 } 542 543 /** 544 * Calls {@link #onSetCaptionEnabled}. 545 */ 546 void setCaptionEnabled(boolean enabled) { 547 onSetCaptionEnabled(enabled); 548 } 549 550 /** 551 * Calls {@link #onSelectTrack}. 552 */ 553 void selectTrack(TvTrackInfo track) { 554 onSelectTrack(track); 555 // TODO: Handle failure. 556 } 557 558 /** 559 * Calls {@link #onUnselectTrack}. 560 */ 561 void unselectTrack(TvTrackInfo track) { 562 onUnselectTrack(track); 563 // TODO: Handle failure. 564 } 565 566 /** 567 * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach 568 * to the overlay window. 569 * 570 * @param windowToken A window token of an application. 571 * @param frame A position of the overlay view. 572 */ 573 void createOverlayView(IBinder windowToken, Rect frame) { 574 if (mOverlayView != null) { 575 mWindowManager.removeView(mOverlayView); 576 mOverlayView = null; 577 } 578 if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")"); 579 mWindowToken = windowToken; 580 mOverlayFrame = frame; 581 if (!mOverlayViewEnabled) { 582 return; 583 } 584 mOverlayView = onCreateOverlayView(); 585 if (mOverlayView == null) { 586 return; 587 } 588 // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create 589 // an overlay window above the media window but below the application window. 590 int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; 591 // We make the overlay view non-focusable and non-touchable so that 592 // the application that owns the window token can decide whether to consume or 593 // dispatch the input events. 594 int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 595 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 596 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 597 mWindowParams = new WindowManager.LayoutParams( 598 frame.right - frame.left, frame.bottom - frame.top, 599 frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT); 600 mWindowParams.privateFlags |= 601 WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; 602 mWindowParams.gravity = Gravity.START | Gravity.TOP; 603 mWindowParams.token = windowToken; 604 mWindowManager.addView(mOverlayView, mWindowParams); 605 } 606 607 /** 608 * Relayouts the current overlay view. 609 * 610 * @param frame A new position of the overlay view. 611 */ 612 void relayoutOverlayView(Rect frame) { 613 if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")"); 614 mOverlayFrame = frame; 615 if (!mOverlayViewEnabled || mOverlayView == null) { 616 return; 617 } 618 mWindowParams.x = frame.left; 619 mWindowParams.y = frame.top; 620 mWindowParams.width = frame.right - frame.left; 621 mWindowParams.height = frame.bottom - frame.top; 622 mWindowManager.updateViewLayout(mOverlayView, mWindowParams); 623 } 624 625 /** 626 * Removes the current overlay view. 627 */ 628 void removeOverlayView(boolean clearWindowToken) { 629 if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")"); 630 if (clearWindowToken) { 631 mWindowToken = null; 632 mOverlayFrame = null; 633 } 634 if (mOverlayView != null) { 635 mWindowManager.removeView(mOverlayView); 636 mOverlayView = null; 637 mWindowParams = null; 638 } 639 } 640 641 /** 642 * Takes care of dispatching incoming input events and tells whether the event was handled. 643 */ 644 int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { 645 if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")"); 646 boolean isNavigationKey = false; 647 if (event instanceof KeyEvent) { 648 KeyEvent keyEvent = (KeyEvent) event; 649 isNavigationKey = isNavigationKey(keyEvent.getKeyCode()); 650 if (keyEvent.dispatch(this, mDispatcherState, this)) { 651 return TvInputManager.Session.DISPATCH_HANDLED; 652 } 653 } else if (event instanceof MotionEvent) { 654 MotionEvent motionEvent = (MotionEvent) event; 655 final int source = motionEvent.getSource(); 656 if (motionEvent.isTouchEvent()) { 657 if (onTouchEvent(motionEvent)) { 658 return TvInputManager.Session.DISPATCH_HANDLED; 659 } 660 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 661 if (onTrackballEvent(motionEvent)) { 662 return TvInputManager.Session.DISPATCH_HANDLED; 663 } 664 } else { 665 if (onGenericMotionEvent(motionEvent)) { 666 return TvInputManager.Session.DISPATCH_HANDLED; 667 } 668 } 669 } 670 if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) { 671 return TvInputManager.Session.DISPATCH_NOT_HANDLED; 672 } 673 if (!mOverlayView.hasWindowFocus()) { 674 mOverlayView.getViewRootImpl().windowFocusChanged(true, true); 675 } 676 if (isNavigationKey && mOverlayView.hasFocusable()) { 677 // If mOverlayView has focusable views, navigation key events should be always 678 // handled. If not, it can make the application UI navigation messed up. 679 // For example, in the case that the left-most view is focused, a left key event 680 // will not be handled in ViewRootImpl. Then, the left key event will be handled in 681 // the application during the UI navigation of the TV input. 682 mOverlayView.getViewRootImpl().dispatchInputEvent(event); 683 return TvInputManager.Session.DISPATCH_HANDLED; 684 } else { 685 mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver); 686 return TvInputManager.Session.DISPATCH_IN_PROGRESS; 687 } 688 } 689 690 private void setSessionCallback(ITvInputSessionCallback callback) { 691 mSessionCallback = callback; 692 } 693 } 694 695 /** @hide */ 696 public static boolean isNavigationKey(int keyCode) { 697 switch (keyCode) { 698 case KeyEvent.KEYCODE_DPAD_LEFT: 699 case KeyEvent.KEYCODE_DPAD_RIGHT: 700 case KeyEvent.KEYCODE_DPAD_UP: 701 case KeyEvent.KEYCODE_DPAD_DOWN: 702 case KeyEvent.KEYCODE_DPAD_CENTER: 703 case KeyEvent.KEYCODE_PAGE_UP: 704 case KeyEvent.KEYCODE_PAGE_DOWN: 705 case KeyEvent.KEYCODE_MOVE_HOME: 706 case KeyEvent.KEYCODE_MOVE_END: 707 case KeyEvent.KEYCODE_TAB: 708 case KeyEvent.KEYCODE_SPACE: 709 case KeyEvent.KEYCODE_ENTER: 710 return true; 711 } 712 return false; 713 } 714 715 @SuppressLint("HandlerLeak") 716 private final class ServiceHandler extends Handler { 717 private static final int DO_CREATE_SESSION = 1; 718 719 @Override 720 public final void handleMessage(Message msg) { 721 switch (msg.what) { 722 case DO_CREATE_SESSION: { 723 SomeArgs args = (SomeArgs) msg.obj; 724 InputChannel channel = (InputChannel) args.arg1; 725 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2; 726 try { 727 Session sessionImpl = onCreateSession(); 728 if (sessionImpl == null) { 729 // Failed to create a session. 730 cb.onSessionCreated(null); 731 } else { 732 sessionImpl.setSessionCallback(cb); 733 ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this, 734 sessionImpl, channel); 735 cb.onSessionCreated(stub); 736 } 737 } catch (RemoteException e) { 738 Log.e(TAG, "error in onSessionCreated"); 739 } 740 args.recycle(); 741 return; 742 } 743 default: { 744 Log.w(TAG, "Unhandled message code: " + msg.what); 745 return; 746 } 747 } 748 } 749 } 750} 751