TvInputManager.java revision ff04ae757a5542d2d5633e75b7adacc4fce1ce7e
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.SystemApi; 20import android.graphics.Rect; 21import android.net.Uri; 22import android.os.Bundle; 23import android.os.Handler; 24import android.os.IBinder; 25import android.os.Looper; 26import android.os.Message; 27import android.os.RemoteException; 28import android.util.ArrayMap; 29import android.util.Log; 30import android.util.Pools.Pool; 31import android.util.Pools.SimplePool; 32import android.util.SparseArray; 33import android.view.InputChannel; 34import android.view.InputEvent; 35import android.view.InputEventSender; 36import android.view.Surface; 37import android.view.View; 38 39import java.util.ArrayList; 40import java.util.Iterator; 41import java.util.LinkedList; 42import java.util.List; 43import java.util.Map; 44 45/** 46 * Central system API to the overall TV input framework (TIF) architecture, which arbitrates 47 * interaction between applications and the selected TV inputs. 48 */ 49public final class TvInputManager { 50 private static final String TAG = "TvInputManager"; 51 52 static final int VIDEO_UNAVAILABLE_REASON_START = 0; 53 static final int VIDEO_UNAVAILABLE_REASON_END = 3; 54 55 /** 56 * A generic reason. Video is not available due to an unspecified error. 57 */ 58 public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = VIDEO_UNAVAILABLE_REASON_START; 59 /** 60 * Video is not available because the TV input is in the middle of tuning to a new channel. 61 */ 62 public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; 63 /** 64 * Video is not available due to the weak TV signal. 65 */ 66 public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2; 67 /** 68 * Video is not available because the TV input stopped the playback temporarily to buffer more 69 * data. 70 */ 71 public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = VIDEO_UNAVAILABLE_REASON_END; 72 73 /** 74 * The TV input is connected. 75 * <p> 76 * State for {@link #getInputState} and {@link 77 * TvInputManager.TvInputListener#onInputStateChanged}. 78 * </p> 79 */ 80 public static final int INPUT_STATE_CONNECTED = 0; 81 /** 82 * The TV input is connected but in standby mode. It would take a while until it becomes 83 * fully ready. 84 * <p> 85 * State for {@link #getInputState} and {@link 86 * TvInputManager.TvInputListener#onInputStateChanged}. 87 * </p> 88 */ 89 public static final int INPUT_STATE_CONNECTED_STANDBY = 1; 90 /** 91 * The TV input is disconnected. 92 * <p> 93 * State for {@link #getInputState} and {@link 94 * TvInputManager.TvInputListener#onInputStateChanged}. 95 * </p> 96 */ 97 public static final int INPUT_STATE_DISCONNECTED = 2; 98 99 /** 100 * Broadcast intent action when the user blocked content ratings change. For use with the 101 * {@link #isRatingBlocked}. 102 */ 103 public static final String ACTION_BLOCKED_RATINGS_CHANGED = 104 "android.media.tv.action.BLOCKED_RATINGS_CHANGED"; 105 106 /** 107 * Broadcast intent action when the parental controls enabled state changes. For use with the 108 * {@link #isParentalControlsEnabled}. 109 */ 110 public static final String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = 111 "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED"; 112 113 private final ITvInputManager mService; 114 115 private final Object mLock = new Object(); 116 117 // @GuardedBy(mLock) 118 private final List<TvInputListenerRecord> mTvInputListenerRecordsList = 119 new LinkedList<TvInputListenerRecord>(); 120 121 // A mapping from TV input ID to the state of corresponding input. 122 // @GuardedBy(mLock) 123 private final Map<String, Integer> mStateMap = new ArrayMap<String, Integer>(); 124 125 // A mapping from the sequence number of a session to its SessionCallbackRecord. 126 private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap = 127 new SparseArray<SessionCallbackRecord>(); 128 129 // A sequence number for the next session to be created. Should be protected by a lock 130 // {@code mSessionCallbackRecordMap}. 131 private int mNextSeq; 132 133 private final ITvInputClient mClient; 134 135 private final ITvInputManagerCallback mCallback; 136 137 private final int mUserId; 138 139 /** 140 * Interface used to receive the created session. 141 * @hide 142 */ 143 @SystemApi 144 public abstract static class SessionCallback { 145 /** 146 * This is called after {@link TvInputManager#createSession} has been processed. 147 * 148 * @param session A {@link TvInputManager.Session} instance created. This can be 149 * {@code null} if the creation request failed. 150 */ 151 public void onSessionCreated(Session session) { 152 } 153 154 /** 155 * This is called when {@link TvInputManager.Session} is released. 156 * This typically happens when the process hosting the session has crashed or been killed. 157 * 158 * @param session A {@link TvInputManager.Session} instance released. 159 */ 160 public void onSessionReleased(Session session) { 161 } 162 163 /** 164 * This is called when the channel of this session is changed by the underlying TV input 165 * with out any {@link TvInputManager.Session#tune(Uri)} request. 166 * 167 * @param session A {@link TvInputManager.Session} associated with this callback. 168 * @param channelUri The URI of a channel. 169 */ 170 public void onChannelRetuned(Session session, Uri channelUri) { 171 } 172 173 /** 174 * This is called when the track information of the session has been changed. 175 * 176 * @param session A {@link TvInputManager.Session} associated with this callback. 177 * @param tracks A list which includes track information. 178 */ 179 public void onTracksChanged(Session session, List<TvTrackInfo> tracks) { 180 } 181 182 /** 183 * This is called when a track for a given type is selected. 184 * 185 * @param session A {@link TvInputManager.Session} associated with this callback 186 * @param type The type of the selected track. The type can be 187 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or 188 * {@link TvTrackInfo#TYPE_SUBTITLE}. 189 * @param trackId The ID of the selected track. When {@code null} the currently selected 190 * track for a given type should be unselected. 191 */ 192 public void onTrackSelected(Session session, int type, String trackId) { 193 } 194 195 /** 196 * This is called when the video is available, so the TV input starts the playback. 197 * 198 * @param session A {@link TvInputManager.Session} associated with this callback. 199 */ 200 public void onVideoAvailable(Session session) { 201 } 202 203 /** 204 * This is called when the video is not available, so the TV input stops the playback. 205 * 206 * @param session A {@link TvInputManager.Session} associated with this callback 207 * @param reason The reason why the TV input stopped the playback: 208 * <ul> 209 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN} 210 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} 211 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL} 212 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING} 213 * </ul> 214 */ 215 public void onVideoUnavailable(Session session, int reason) { 216 } 217 218 /** 219 * This is called when the current program content turns out to be allowed to watch since 220 * its content rating is not blocked by parental controls. 221 * 222 * @param session A {@link TvInputManager.Session} associated with this callback 223 */ 224 public void onContentAllowed(Session session) { 225 } 226 227 /** 228 * This is called when the current program content turns out to be not allowed to watch 229 * since its content rating is blocked by parental controls. 230 * 231 * @param session A {@link TvInputManager.Session} associated with this callback 232 * @param rating The content ration of the blocked program. 233 */ 234 public void onContentBlocked(Session session, TvContentRating rating) { 235 } 236 237 /** 238 * This is called when {@link TvInputService.Session#layoutSurface} is called to 239 * change the layout of surface. 240 * 241 * @param session A {@link TvInputManager.Session} associated with this callback 242 * @param l Left position. 243 * @param t Top position. 244 * @param r Right position. 245 * @param b Bottom position. 246 * @hide 247 */ 248 @SystemApi 249 public void onLayoutSurface(Session session, int left, int top, int right, int bottom) { 250 } 251 252 /** 253 * This is called when a custom event has been sent from this session. 254 * 255 * @param session A {@link TvInputManager.Session} associated with this callback 256 * @param eventType The type of the event. 257 * @param eventArgs Optional arguments of the event. 258 * @hide 259 */ 260 @SystemApi 261 public void onSessionEvent(Session session, String eventType, Bundle eventArgs) { 262 } 263 } 264 265 private static final class SessionCallbackRecord { 266 private final SessionCallback mSessionCallback; 267 private final Handler mHandler; 268 private Session mSession; 269 270 public SessionCallbackRecord(SessionCallback sessionCallback, 271 Handler handler) { 272 mSessionCallback = sessionCallback; 273 mHandler = handler; 274 } 275 276 public void postSessionCreated(final Session session) { 277 mSession = session; 278 mHandler.post(new Runnable() { 279 @Override 280 public void run() { 281 mSessionCallback.onSessionCreated(session); 282 } 283 }); 284 } 285 286 public void postSessionReleased() { 287 mHandler.post(new Runnable() { 288 @Override 289 public void run() { 290 mSessionCallback.onSessionReleased(mSession); 291 } 292 }); 293 } 294 295 public void postChannelRetuned(final Uri channelUri) { 296 mHandler.post(new Runnable() { 297 @Override 298 public void run() { 299 mSessionCallback.onChannelRetuned(mSession, channelUri); 300 } 301 }); 302 } 303 304 public void postTracksChanged(final List<TvTrackInfo> tracks) { 305 mHandler.post(new Runnable() { 306 @Override 307 public void run() { 308 mSession.mAudioTracks.clear(); 309 mSession.mVideoTracks.clear(); 310 mSession.mSubtitleTracks.clear(); 311 for (TvTrackInfo track : tracks) { 312 if (track.getType() == TvTrackInfo.TYPE_AUDIO) { 313 mSession.mAudioTracks.add(track); 314 } else if (track.getType() == TvTrackInfo.TYPE_VIDEO) { 315 mSession.mVideoTracks.add(track); 316 } else if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) { 317 mSession.mSubtitleTracks.add(track); 318 } else { 319 // Silently ignore. 320 } 321 } 322 mSessionCallback.onTracksChanged(mSession, tracks); 323 } 324 }); 325 } 326 327 public void postTrackSelected(final int type, final String trackId) { 328 mHandler.post(new Runnable() { 329 @Override 330 public void run() { 331 if (type == TvTrackInfo.TYPE_AUDIO) { 332 mSession.mSelectedAudioTrackId = trackId; 333 } else if (type == TvTrackInfo.TYPE_VIDEO) { 334 mSession.mSelectedVideoTrackId = trackId; 335 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 336 mSession.mSelectedSubtitleTrackId = trackId; 337 } else { 338 // Silently ignore. 339 return; 340 } 341 mSessionCallback.onTrackSelected(mSession, type, trackId); 342 } 343 }); 344 } 345 346 public void postVideoAvailable() { 347 mHandler.post(new Runnable() { 348 @Override 349 public void run() { 350 mSessionCallback.onVideoAvailable(mSession); 351 } 352 }); 353 } 354 355 public void postVideoUnavailable(final int reason) { 356 mHandler.post(new Runnable() { 357 @Override 358 public void run() { 359 mSessionCallback.onVideoUnavailable(mSession, reason); 360 } 361 }); 362 } 363 364 public void postContentAllowed() { 365 mHandler.post(new Runnable() { 366 @Override 367 public void run() { 368 mSessionCallback.onContentAllowed(mSession); 369 } 370 }); 371 } 372 373 public void postContentBlocked(final TvContentRating rating) { 374 mHandler.post(new Runnable() { 375 @Override 376 public void run() { 377 mSessionCallback.onContentBlocked(mSession, rating); 378 } 379 }); 380 } 381 382 public void postLayoutSurface(final int left, final int top, final int right, 383 final int bottom) { 384 mHandler.post(new Runnable() { 385 @Override 386 public void run() { 387 mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom); 388 } 389 }); 390 } 391 392 public void postSessionEvent(final String eventType, final Bundle eventArgs) { 393 mHandler.post(new Runnable() { 394 @Override 395 public void run() { 396 mSessionCallback.onSessionEvent(mSession, eventType, eventArgs); 397 } 398 }); 399 } 400 } 401 402 /** 403 * Interface used to monitor status of the TV input. 404 */ 405 public abstract static class TvInputListener { 406 /** 407 * This is called when the state of a given TV input is changed. 408 * 409 * @param inputId The id of the TV input. 410 * @param state State of the TV input. The value is one of the following: 411 * <ul> 412 * <li>{@link TvInputManager#INPUT_STATE_CONNECTED} 413 * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY} 414 * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED} 415 * </ul> 416 */ 417 public void onInputStateChanged(String inputId, int state) { 418 } 419 420 /** 421 * This is called when a TV input is added. 422 * 423 * @param inputId The id of the TV input. 424 */ 425 public void onInputAdded(String inputId) { 426 } 427 428 /** 429 * This is called when a TV input is removed. 430 * 431 * @param inputId The id of the TV input. 432 */ 433 public void onInputRemoved(String inputId) { 434 } 435 } 436 437 private static final class TvInputListenerRecord { 438 private final TvInputListener mListener; 439 private final Handler mHandler; 440 441 public TvInputListenerRecord(TvInputListener listener, Handler handler) { 442 mListener = listener; 443 mHandler = handler; 444 } 445 446 public TvInputListener getListener() { 447 return mListener; 448 } 449 450 public void postInputStateChanged(final String inputId, final int state) { 451 mHandler.post(new Runnable() { 452 @Override 453 public void run() { 454 mListener.onInputStateChanged(inputId, state); 455 } 456 }); 457 } 458 459 public void postInputAdded(final String inputId) { 460 mHandler.post(new Runnable() { 461 @Override 462 public void run() { 463 mListener.onInputAdded(inputId); 464 } 465 }); 466 } 467 468 public void postInputRemoved(final String inputId) { 469 mHandler.post(new Runnable() { 470 @Override 471 public void run() { 472 mListener.onInputRemoved(inputId); 473 } 474 }); 475 } 476 } 477 478 /** 479 * @hide 480 */ 481 public TvInputManager(ITvInputManager service, int userId) { 482 mService = service; 483 mUserId = userId; 484 mClient = new ITvInputClient.Stub() { 485 @Override 486 public void onSessionCreated(String inputId, IBinder token, InputChannel channel, 487 int seq) { 488 synchronized (mSessionCallbackRecordMap) { 489 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 490 if (record == null) { 491 Log.e(TAG, "Callback not found for " + token); 492 return; 493 } 494 Session session = null; 495 if (token != null) { 496 session = new Session(token, channel, mService, mUserId, seq, 497 mSessionCallbackRecordMap); 498 } 499 record.postSessionCreated(session); 500 } 501 } 502 503 @Override 504 public void onSessionReleased(int seq) { 505 synchronized (mSessionCallbackRecordMap) { 506 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 507 mSessionCallbackRecordMap.delete(seq); 508 if (record == null) { 509 Log.e(TAG, "Callback not found for seq:" + seq); 510 return; 511 } 512 record.mSession.releaseInternal(); 513 record.postSessionReleased(); 514 } 515 } 516 517 @Override 518 public void onChannelRetuned(Uri channelUri, int seq) { 519 synchronized (mSessionCallbackRecordMap) { 520 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 521 if (record == null) { 522 Log.e(TAG, "Callback not found for seq " + seq); 523 return; 524 } 525 record.postChannelRetuned(channelUri); 526 } 527 } 528 529 @Override 530 public void onTracksChanged(List<TvTrackInfo> tracks, int seq) { 531 synchronized (mSessionCallbackRecordMap) { 532 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 533 if (record == null) { 534 Log.e(TAG, "Callback not found for seq " + seq); 535 return; 536 } 537 record.postTracksChanged(tracks); 538 } 539 } 540 541 @Override 542 public void onTrackSelected(int type, String trackId, int seq) { 543 synchronized (mSessionCallbackRecordMap) { 544 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 545 if (record == null) { 546 Log.e(TAG, "Callback not found for seq " + seq); 547 return; 548 } 549 record.postTrackSelected(type, trackId); 550 } 551 } 552 553 @Override 554 public void onVideoAvailable(int seq) { 555 synchronized (mSessionCallbackRecordMap) { 556 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 557 if (record == null) { 558 Log.e(TAG, "Callback not found for seq " + seq); 559 return; 560 } 561 record.postVideoAvailable(); 562 } 563 } 564 565 @Override 566 public void onVideoUnavailable(int reason, int seq) { 567 synchronized (mSessionCallbackRecordMap) { 568 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 569 if (record == null) { 570 Log.e(TAG, "Callback not found for seq " + seq); 571 return; 572 } 573 record.postVideoUnavailable(reason); 574 } 575 } 576 577 @Override 578 public void onContentAllowed(int seq) { 579 synchronized (mSessionCallbackRecordMap) { 580 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 581 if (record == null) { 582 Log.e(TAG, "Callback not found for seq " + seq); 583 return; 584 } 585 record.postContentAllowed(); 586 } 587 } 588 589 @Override 590 public void onContentBlocked(String rating, int seq) { 591 synchronized (mSessionCallbackRecordMap) { 592 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 593 if (record == null) { 594 Log.e(TAG, "Callback not found for seq " + seq); 595 return; 596 } 597 record.postContentBlocked(TvContentRating.unflattenFromString(rating)); 598 } 599 } 600 601 @Override 602 public void onLayoutSurface(int left, int top, int right, int bottom, int seq) { 603 synchronized (mSessionCallbackRecordMap) { 604 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 605 if (record == null) { 606 Log.e(TAG, "Callback not found for seq " + seq); 607 return; 608 } 609 record.postLayoutSurface(left, top, right, bottom); 610 } 611 } 612 613 @Override 614 public void onSessionEvent(String eventType, Bundle eventArgs, int seq) { 615 synchronized (mSessionCallbackRecordMap) { 616 SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); 617 if (record == null) { 618 Log.e(TAG, "Callback not found for seq " + seq); 619 return; 620 } 621 record.postSessionEvent(eventType, eventArgs); 622 } 623 } 624 }; 625 mCallback = new ITvInputManagerCallback.Stub() { 626 @Override 627 public void onInputStateChanged(String inputId, int state) { 628 synchronized (mLock) { 629 mStateMap.put(inputId, state); 630 for (TvInputListenerRecord record : mTvInputListenerRecordsList) { 631 record.postInputStateChanged(inputId, state); 632 } 633 } 634 } 635 636 @Override 637 public void onInputAdded(String inputId) { 638 synchronized (mLock) { 639 mStateMap.put(inputId, INPUT_STATE_CONNECTED); 640 for (TvInputListenerRecord record : mTvInputListenerRecordsList) { 641 record.postInputAdded(inputId); 642 } 643 } 644 } 645 646 @Override 647 public void onInputRemoved(String inputId) { 648 synchronized (mLock) { 649 mStateMap.remove(inputId); 650 for (TvInputListenerRecord record : mTvInputListenerRecordsList) { 651 record.postInputRemoved(inputId); 652 } 653 } 654 } 655 }; 656 try { 657 mService.registerCallback(mCallback, mUserId); 658 } catch (RemoteException e) { 659 Log.e(TAG, "mService.registerCallback failed: " + e); 660 } 661 } 662 663 /** 664 * Returns the complete list of TV inputs on the system. 665 * 666 * @return List of {@link TvInputInfo} for each TV input that describes its meta information. 667 */ 668 public List<TvInputInfo> getTvInputList() { 669 try { 670 return mService.getTvInputList(mUserId); 671 } catch (RemoteException e) { 672 throw new RuntimeException(e); 673 } 674 } 675 676 /** 677 * Returns the {@link TvInputInfo} for a given TV input. 678 * 679 * @param inputId The ID of the TV input. 680 * @return the {@link TvInputInfo} for a given TV input. {@code null} if not found. 681 */ 682 public TvInputInfo getTvInputInfo(String inputId) { 683 if (inputId == null) { 684 throw new IllegalArgumentException("inputId cannot be null"); 685 } 686 try { 687 return mService.getTvInputInfo(inputId, mUserId); 688 } catch (RemoteException e) { 689 throw new RuntimeException(e); 690 } 691 } 692 693 /** 694 * Returns the state of a given TV input. It retuns one of the following: 695 * <ul> 696 * <li>{@link #INPUT_STATE_CONNECTED} 697 * <li>{@link #INPUT_STATE_CONNECTED_STANDBY} 698 * <li>{@link #INPUT_STATE_DISCONNECTED} 699 * </ul> 700 * 701 * @param inputId The id of the TV input. 702 * @throws IllegalArgumentException if the argument is {@code null} or if there is no 703 * {@link TvInputInfo} corresponding to {@code inputId}. 704 */ 705 public int getInputState(String inputId) { 706 if (inputId == null) { 707 throw new IllegalArgumentException("inputId cannot be null"); 708 } 709 synchronized (mLock) { 710 Integer state = mStateMap.get(inputId); 711 if (state == null) { 712 throw new IllegalArgumentException("Unrecognized input ID: " + inputId); 713 } 714 return state.intValue(); 715 } 716 } 717 718 /** 719 * Registers a {@link TvInputListener}. 720 * 721 * @param listener A listener used to monitor status of the TV inputs. 722 * @param handler A {@link Handler} that the status change will be delivered to. 723 * @throws IllegalArgumentException if any of the arguments is {@code null}. 724 */ 725 public void registerListener(TvInputListener listener, Handler handler) { 726 if (listener == null) { 727 throw new IllegalArgumentException("callback cannot be null"); 728 } 729 if (handler == null) { 730 throw new IllegalArgumentException("handler cannot be null"); 731 } 732 synchronized (mLock) { 733 mTvInputListenerRecordsList.add(new TvInputListenerRecord(listener, handler)); 734 } 735 } 736 737 /** 738 * Unregisters the existing {@link TvInputListener}. 739 * 740 * @param listener The existing listener to remove. 741 * @throws IllegalArgumentException if any of the arguments is {@code null}. 742 */ 743 public void unregisterListener(final TvInputListener listener) { 744 if (listener == null) { 745 throw new IllegalArgumentException("callback cannot be null"); 746 } 747 synchronized (mLock) { 748 for (Iterator<TvInputListenerRecord> it = mTvInputListenerRecordsList.iterator(); 749 it.hasNext(); ) { 750 TvInputListenerRecord record = it.next(); 751 if (record.getListener() == listener) { 752 it.remove(); 753 break; 754 } 755 } 756 } 757 } 758 759 /** 760 * Returns the user's parental controls enabled state. 761 * 762 * @return {@code true} if the user enabled the parental controls, {@code false} otherwise. 763 */ 764 public boolean isParentalControlsEnabled() { 765 try { 766 return mService.isParentalControlsEnabled(mUserId); 767 } catch (RemoteException e) { 768 throw new RuntimeException(e); 769 } 770 } 771 772 /** 773 * Sets the user's parental controls enabled state. 774 * 775 * @param enabled The user's parental controls enabled state. {@code true} if the user enabled 776 * the parental controls, {@code false} otherwise. 777 * @see #isParentalControlsEnabled 778 * @hide 779 */ 780 @SystemApi 781 public void setParentalControlsEnabled(boolean enabled) { 782 try { 783 mService.setParentalControlsEnabled(enabled, mUserId); 784 } catch (RemoteException e) { 785 throw new RuntimeException(e); 786 } 787 } 788 789 /** 790 * Checks whether a given TV content rating is blocked by the user. 791 * 792 * @param rating The TV content rating to check. 793 * @return {@code true} if the given TV content rating is blocked, {@code false} otherwise. 794 */ 795 public boolean isRatingBlocked(TvContentRating rating) { 796 if (rating == null) { 797 throw new IllegalArgumentException("rating cannot be null"); 798 } 799 try { 800 return mService.isRatingBlocked(rating.flattenToString(), mUserId); 801 } catch (RemoteException e) { 802 throw new RuntimeException(e); 803 } 804 } 805 806 /** 807 * Returns the list of blocked content ratings. 808 * 809 * @return the list of content ratings blocked by the user. 810 * @hide 811 */ 812 @SystemApi 813 public List<TvContentRating> getBlockedRatings() { 814 try { 815 List<TvContentRating> ratings = new ArrayList<TvContentRating>(); 816 for (String rating : mService.getBlockedRatings(mUserId)) { 817 ratings.add(TvContentRating.unflattenFromString(rating)); 818 } 819 return ratings; 820 } catch (RemoteException e) { 821 throw new RuntimeException(e); 822 } 823 } 824 825 /** 826 * Adds a user blocked content rating. 827 * 828 * @param rating The content rating to block. 829 * @see #isRatingBlocked 830 * @see #removeBlockedRating 831 * @hide 832 */ 833 @SystemApi 834 public void addBlockedRating(TvContentRating rating) { 835 if (rating == null) { 836 throw new IllegalArgumentException("rating cannot be null"); 837 } 838 try { 839 mService.addBlockedRating(rating.flattenToString(), mUserId); 840 } catch (RemoteException e) { 841 throw new RuntimeException(e); 842 } 843 } 844 845 /** 846 * Removes a user blocked content rating. 847 * 848 * @param rating The content rating to unblock. 849 * @see #isRatingBlocked 850 * @see #addBlockedRating 851 * @hide 852 */ 853 @SystemApi 854 public void removeBlockedRating(TvContentRating rating) { 855 if (rating == null) { 856 throw new IllegalArgumentException("rating cannot be null"); 857 } 858 try { 859 mService.removeBlockedRating(rating.flattenToString(), mUserId); 860 } catch (RemoteException e) { 861 throw new RuntimeException(e); 862 } 863 } 864 865 /** 866 * Returns the list of xml resource uris for TV content rating systems. 867 * @hide 868 */ 869 @SystemApi 870 public List<Uri> getTvContentRatingSystemXmls() { 871 try { 872 return mService.getTvContentRatingSystemXmls(mUserId); 873 } catch (RemoteException e) { 874 throw new RuntimeException(e); 875 } 876 } 877 878 /** 879 * Creates a {@link Session} for a given TV input. 880 * <p> 881 * The number of sessions that can be created at the same time is limited by the capability of 882 * the given TV input. 883 * </p> 884 * 885 * @param inputId The id of the TV input. 886 * @param callback A callback used to receive the created session. 887 * @param handler A {@link Handler} that the session creation will be delivered to. 888 * @throws IllegalArgumentException if any of the arguments is {@code null}. 889 * @hide 890 */ 891 @SystemApi 892 public void createSession(String inputId, final SessionCallback callback, 893 Handler handler) { 894 if (inputId == null) { 895 throw new IllegalArgumentException("id cannot be null"); 896 } 897 if (callback == null) { 898 throw new IllegalArgumentException("callback cannot be null"); 899 } 900 if (handler == null) { 901 throw new IllegalArgumentException("handler cannot be null"); 902 } 903 SessionCallbackRecord record = new SessionCallbackRecord(callback, handler); 904 synchronized (mSessionCallbackRecordMap) { 905 int seq = mNextSeq++; 906 mSessionCallbackRecordMap.put(seq, record); 907 try { 908 mService.createSession(mClient, inputId, seq, mUserId); 909 } catch (RemoteException e) { 910 throw new RuntimeException(e); 911 } 912 } 913 } 914 915 /** 916 * Returns the TvStreamConfig list of the given TV input. 917 * 918 * @param inputId the id of the TV input. 919 * @return List of {@link TvStreamConfig} which is available for capturing 920 * of the given TV input. 921 * @hide 922 */ 923 @SystemApi 924 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId) { 925 try { 926 return mService.getAvailableTvStreamConfigList(inputId, mUserId); 927 } catch (RemoteException e) { 928 throw new RuntimeException(e); 929 } 930 } 931 932 /** 933 * Take a snapshot of the given TV input into the provided Surface. 934 * 935 * @param inputId the id of the TV input. 936 * @param surface the {@link Surface} to which the snapshot is captured. 937 * @param config the {@link TvStreamConfig} which is used for capturing. 938 * @return true when the {@link Surface} is ready to be captured. 939 * @hide 940 */ 941 @SystemApi 942 public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config) { 943 try { 944 return mService.captureFrame(inputId, surface, config, mUserId); 945 } catch (RemoteException e) { 946 throw new RuntimeException(e); 947 } 948 } 949 950 /** 951 * The Session provides the per-session functionality of TV inputs. 952 * @hide 953 */ 954 @SystemApi 955 public static final class Session { 956 static final int DISPATCH_IN_PROGRESS = -1; 957 static final int DISPATCH_NOT_HANDLED = 0; 958 static final int DISPATCH_HANDLED = 1; 959 960 private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500; 961 962 private final ITvInputManager mService; 963 private final int mUserId; 964 private final int mSeq; 965 966 // For scheduling input event handling on the main thread. This also serves as a lock to 967 // protect pending input events and the input channel. 968 private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper()); 969 970 private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20); 971 private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20); 972 private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; 973 974 private IBinder mToken; 975 private TvInputEventSender mSender; 976 private InputChannel mChannel; 977 private final List<TvTrackInfo> mAudioTracks = new ArrayList<TvTrackInfo>(); 978 private final List<TvTrackInfo> mVideoTracks = new ArrayList<TvTrackInfo>(); 979 private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<TvTrackInfo>(); 980 private String mSelectedAudioTrackId; 981 private String mSelectedVideoTrackId; 982 private String mSelectedSubtitleTrackId; 983 984 private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId, 985 int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { 986 mToken = token; 987 mChannel = channel; 988 mService = service; 989 mUserId = userId; 990 mSeq = seq; 991 mSessionCallbackRecordMap = sessionCallbackRecordMap; 992 } 993 994 /** 995 * Releases this session. 996 */ 997 public void release() { 998 if (mToken == null) { 999 Log.w(TAG, "The session has been already released"); 1000 return; 1001 } 1002 try { 1003 mService.releaseSession(mToken, mUserId); 1004 } catch (RemoteException e) { 1005 throw new RuntimeException(e); 1006 } 1007 1008 releaseInternal(); 1009 } 1010 1011 /** 1012 * Sets this as main session. See {@link TvView#setMainTvView} for about meaning of "main". 1013 * @hide 1014 */ 1015 public void setMainSession() { 1016 if (mToken == null) { 1017 Log.w(TAG, "The session has been already released"); 1018 return; 1019 } 1020 try { 1021 mService.setMainSession(mToken, mUserId); 1022 } catch (RemoteException e) { 1023 throw new RuntimeException(e); 1024 } 1025 } 1026 1027 /** 1028 * Sets the {@link android.view.Surface} for this session. 1029 * 1030 * @param surface A {@link android.view.Surface} used to render video. 1031 */ 1032 public void setSurface(Surface surface) { 1033 if (mToken == null) { 1034 Log.w(TAG, "The session has been already released"); 1035 return; 1036 } 1037 // surface can be null. 1038 try { 1039 mService.setSurface(mToken, surface, mUserId); 1040 } catch (RemoteException e) { 1041 throw new RuntimeException(e); 1042 } 1043 } 1044 1045 /** 1046 * Notifies of any structural changes (format or size) of the {@link Surface} 1047 * passed by {@link #setSurface}. 1048 * 1049 * @param format The new PixelFormat of the {@link Surface}. 1050 * @param width The new width of the {@link Surface}. 1051 * @param height The new height of the {@link Surface}. 1052 * @hide 1053 */ 1054 @SystemApi 1055 public void dispatchSurfaceChanged(int format, int width, int height) { 1056 if (mToken == null) { 1057 Log.w(TAG, "The session has been already released"); 1058 return; 1059 } 1060 try { 1061 mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId); 1062 } catch (RemoteException e) { 1063 throw new RuntimeException(e); 1064 } 1065 } 1066 1067 /** 1068 * Sets the relative stream volume of this session to handle a change of audio focus. 1069 * 1070 * @param volume A volume value between 0.0f to 1.0f. 1071 * @throws IllegalArgumentException if the volume value is out of range. 1072 */ 1073 public void setStreamVolume(float volume) { 1074 if (mToken == null) { 1075 Log.w(TAG, "The session has been already released"); 1076 return; 1077 } 1078 try { 1079 if (volume < 0.0f || volume > 1.0f) { 1080 throw new IllegalArgumentException("volume should be between 0.0f and 1.0f"); 1081 } 1082 mService.setVolume(mToken, volume, mUserId); 1083 } catch (RemoteException e) { 1084 throw new RuntimeException(e); 1085 } 1086 } 1087 1088 /** 1089 * Tunes to a given channel. 1090 * 1091 * @param channelUri The URI of a channel. 1092 * @throws IllegalArgumentException if the argument is {@code null}. 1093 */ 1094 public void tune(Uri channelUri) { 1095 tune(channelUri, null); 1096 } 1097 1098 /** 1099 * Tunes to a given channel. 1100 * 1101 * @param channelUri The URI of a channel. 1102 * @param params A set of extra parameters which might be handled with this tune event. 1103 * @throws IllegalArgumentException if {@code channelUri} is {@code null}. 1104 * @hide 1105 */ 1106 @SystemApi 1107 public void tune(Uri channelUri, Bundle params) { 1108 if (channelUri == null) { 1109 throw new IllegalArgumentException("channelUri cannot be null"); 1110 } 1111 if (mToken == null) { 1112 Log.w(TAG, "The session has been already released"); 1113 return; 1114 } 1115 mAudioTracks.clear(); 1116 mVideoTracks.clear(); 1117 mSubtitleTracks.clear(); 1118 mSelectedAudioTrackId = null; 1119 mSelectedVideoTrackId = null; 1120 mSelectedSubtitleTrackId = null; 1121 try { 1122 mService.tune(mToken, channelUri, params, mUserId); 1123 } catch (RemoteException e) { 1124 throw new RuntimeException(e); 1125 } 1126 } 1127 1128 /** 1129 * Enables or disables the caption for this session. 1130 * 1131 * @param enabled {@code true} to enable, {@code false} to disable. 1132 */ 1133 public void setCaptionEnabled(boolean enabled) { 1134 if (mToken == null) { 1135 Log.w(TAG, "The session has been already released"); 1136 return; 1137 } 1138 try { 1139 mService.setCaptionEnabled(mToken, enabled, mUserId); 1140 } catch (RemoteException e) { 1141 throw new RuntimeException(e); 1142 } 1143 } 1144 1145 /** 1146 * Selects a track. 1147 * 1148 * @param type The type of the track to select. The type can be 1149 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or 1150 * {@link TvTrackInfo#TYPE_SUBTITLE}. 1151 * @param trackId The ID of the track to select. When {@code null}, the currently selected 1152 * track of the given type will be unselected. 1153 * @see #getTracks() 1154 */ 1155 public void selectTrack(int type, String trackId) { 1156 if (type == TvTrackInfo.TYPE_AUDIO) { 1157 if (trackId != null && !mAudioTracks.contains(trackId)) { 1158 Log.w(TAG, "Invalid audio trackId: " + trackId); 1159 } 1160 } else if (type == TvTrackInfo.TYPE_VIDEO) { 1161 if (trackId != null && !mVideoTracks.contains(trackId)) { 1162 Log.w(TAG, "Invalid video trackId: " + trackId); 1163 } 1164 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 1165 if (trackId != null && !mSubtitleTracks.contains(trackId)) { 1166 Log.w(TAG, "Invalid subtitle trackId: " + trackId); 1167 } 1168 } else { 1169 throw new IllegalArgumentException("invalid type: " + type); 1170 } 1171 if (mToken == null) { 1172 Log.w(TAG, "The session has been already released"); 1173 return; 1174 } 1175 try { 1176 mService.selectTrack(mToken, type, trackId, mUserId); 1177 } catch (RemoteException e) { 1178 throw new RuntimeException(e); 1179 } 1180 } 1181 1182 /** 1183 * Returns the list of tracks for a given type. Returns {@code null} if the information is 1184 * not available. 1185 * 1186 * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO}, 1187 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}. 1188 * @return the list of tracks for the given type. 1189 */ 1190 public List<TvTrackInfo> getTracks(int type) { 1191 if (type == TvTrackInfo.TYPE_AUDIO) { 1192 if (mAudioTracks == null) { 1193 return null; 1194 } 1195 return mAudioTracks; 1196 } else if (type == TvTrackInfo.TYPE_VIDEO) { 1197 if (mVideoTracks == null) { 1198 return null; 1199 } 1200 return mVideoTracks; 1201 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 1202 if (mSubtitleTracks == null) { 1203 return null; 1204 } 1205 return mSubtitleTracks; 1206 } 1207 throw new IllegalArgumentException("invalid type: " + type); 1208 } 1209 1210 /** 1211 * Returns the selected track for a given type. Returns {@code null} if the information is 1212 * not available or any of the tracks for the given type is not selected. 1213 * 1214 * @return the ID of the selected track. 1215 * @see #selectTrack 1216 */ 1217 public String getSelectedTrack(int type) { 1218 if (type == TvTrackInfo.TYPE_AUDIO) { 1219 return mSelectedAudioTrackId; 1220 } else if (type == TvTrackInfo.TYPE_VIDEO) { 1221 return mSelectedVideoTrackId; 1222 } else if (type == TvTrackInfo.TYPE_SUBTITLE) { 1223 return mSelectedSubtitleTrackId; 1224 } 1225 throw new IllegalArgumentException("invalid type: " + type); 1226 } 1227 1228 /** 1229 * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle) 1230 * TvInputService.Session.appPrivateCommand()} on the current TvView. 1231 * 1232 * @param action Name of the command to be performed. This <em>must</em> be a scoped name, 1233 * i.e. prefixed with a package name you own, so that different developers will 1234 * not create conflicting commands. 1235 * @param data Any data to include with the command. 1236 * @hide 1237 */ 1238 @SystemApi 1239 public void sendAppPrivateCommand(String action, Bundle data) { 1240 if (mToken == null) { 1241 Log.w(TAG, "The session has been already released"); 1242 return; 1243 } 1244 try { 1245 mService.sendAppPrivateCommand(mToken, action, data, mUserId); 1246 } catch (RemoteException e) { 1247 throw new RuntimeException(e); 1248 } 1249 } 1250 1251 /** 1252 * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView} 1253 * should be called whenever the layout of its containing view is changed. 1254 * {@link #removeOverlayView()} should be called to remove the overlay view. 1255 * Since a session can have only one overlay view, this method should be called only once 1256 * or it can be called again after calling {@link #removeOverlayView()}. 1257 * 1258 * @param view A view playing TV. 1259 * @param frame A position of the overlay view. 1260 * @throws IllegalArgumentException if any of the arguments is {@code null}. 1261 * @throws IllegalStateException if {@code view} is not attached to a window. 1262 */ 1263 void createOverlayView(View view, Rect frame) { 1264 if (view == null) { 1265 throw new IllegalArgumentException("view cannot be null"); 1266 } 1267 if (frame == null) { 1268 throw new IllegalArgumentException("frame cannot be null"); 1269 } 1270 if (view.getWindowToken() == null) { 1271 throw new IllegalStateException("view must be attached to a window"); 1272 } 1273 if (mToken == null) { 1274 Log.w(TAG, "The session has been already released"); 1275 return; 1276 } 1277 try { 1278 mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId); 1279 } catch (RemoteException e) { 1280 throw new RuntimeException(e); 1281 } 1282 } 1283 1284 /** 1285 * Relayouts the current overlay view. 1286 * 1287 * @param frame A new position of the overlay view. 1288 * @throws IllegalArgumentException if the arguments is {@code null}. 1289 */ 1290 void relayoutOverlayView(Rect frame) { 1291 if (frame == null) { 1292 throw new IllegalArgumentException("frame cannot be null"); 1293 } 1294 if (mToken == null) { 1295 Log.w(TAG, "The session has been already released"); 1296 return; 1297 } 1298 try { 1299 mService.relayoutOverlayView(mToken, frame, mUserId); 1300 } catch (RemoteException e) { 1301 throw new RuntimeException(e); 1302 } 1303 } 1304 1305 /** 1306 * Removes the current overlay view. 1307 */ 1308 void removeOverlayView() { 1309 if (mToken == null) { 1310 Log.w(TAG, "The session has been already released"); 1311 return; 1312 } 1313 try { 1314 mService.removeOverlayView(mToken, mUserId); 1315 } catch (RemoteException e) { 1316 throw new RuntimeException(e); 1317 } 1318 } 1319 1320 /** 1321 * Requests to unblock content blocked by parental controls. 1322 */ 1323 void requestUnblockContent(TvContentRating unblockedRating) { 1324 if (mToken == null) { 1325 Log.w(TAG, "The session has been already released"); 1326 return; 1327 } 1328 try { 1329 mService.requestUnblockContent(mToken, unblockedRating.flattenToString(), mUserId); 1330 } catch (RemoteException e) { 1331 throw new RuntimeException(e); 1332 } 1333 } 1334 1335 /** 1336 * Dispatches an input event to this session. 1337 * 1338 * @param event An {@link InputEvent} to dispatch. 1339 * @param token A token used to identify the input event later in the callback. 1340 * @param callback A callback used to receive the dispatch result. 1341 * @param handler A {@link Handler} that the dispatch result will be delivered to. 1342 * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns 1343 * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns 1344 * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will 1345 * be invoked later. 1346 * @throws IllegalArgumentException if any of the necessary arguments is {@code null}. 1347 * @hide 1348 */ 1349 public int dispatchInputEvent(InputEvent event, Object token, 1350 FinishedInputEventCallback callback, Handler handler) { 1351 if (event == null) { 1352 throw new IllegalArgumentException("event cannot be null"); 1353 } 1354 if (callback != null && handler == null) { 1355 throw new IllegalArgumentException("handler cannot be null"); 1356 } 1357 synchronized (mHandler) { 1358 if (mChannel == null) { 1359 return DISPATCH_NOT_HANDLED; 1360 } 1361 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler); 1362 if (Looper.myLooper() == Looper.getMainLooper()) { 1363 // Already running on the main thread so we can send the event immediately. 1364 return sendInputEventOnMainLooperLocked(p); 1365 } 1366 1367 // Post the event to the main thread. 1368 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p); 1369 msg.setAsynchronous(true); 1370 mHandler.sendMessage(msg); 1371 return DISPATCH_IN_PROGRESS; 1372 } 1373 } 1374 1375 /** 1376 * Callback that is invoked when an input event that was dispatched to this session has been 1377 * finished. 1378 * 1379 * @hide 1380 */ 1381 public interface FinishedInputEventCallback { 1382 /** 1383 * Called when the dispatched input event is finished. 1384 * 1385 * @param token A token passed to {@link #dispatchInputEvent}. 1386 * @param handled {@code true} if the dispatched input event was handled properly. 1387 * {@code false} otherwise. 1388 */ 1389 public void onFinishedInputEvent(Object token, boolean handled); 1390 } 1391 1392 // Must be called on the main looper 1393 private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { 1394 synchronized (mHandler) { 1395 int result = sendInputEventOnMainLooperLocked(p); 1396 if (result == DISPATCH_IN_PROGRESS) { 1397 return; 1398 } 1399 } 1400 1401 invokeFinishedInputEventCallback(p, false); 1402 } 1403 1404 private int sendInputEventOnMainLooperLocked(PendingEvent p) { 1405 if (mChannel != null) { 1406 if (mSender == null) { 1407 mSender = new TvInputEventSender(mChannel, mHandler.getLooper()); 1408 } 1409 1410 final InputEvent event = p.mEvent; 1411 final int seq = event.getSequenceNumber(); 1412 if (mSender.sendInputEvent(seq, event)) { 1413 mPendingEvents.put(seq, p); 1414 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); 1415 msg.setAsynchronous(true); 1416 mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT); 1417 return DISPATCH_IN_PROGRESS; 1418 } 1419 1420 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:" 1421 + event); 1422 } 1423 return DISPATCH_NOT_HANDLED; 1424 } 1425 1426 void finishedInputEvent(int seq, boolean handled, boolean timeout) { 1427 final PendingEvent p; 1428 synchronized (mHandler) { 1429 int index = mPendingEvents.indexOfKey(seq); 1430 if (index < 0) { 1431 return; // spurious, event already finished or timed out 1432 } 1433 1434 p = mPendingEvents.valueAt(index); 1435 mPendingEvents.removeAt(index); 1436 1437 if (timeout) { 1438 Log.w(TAG, "Timeout waiting for seesion to handle input event after " 1439 + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); 1440 } else { 1441 mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); 1442 } 1443 } 1444 1445 invokeFinishedInputEventCallback(p, handled); 1446 } 1447 1448 // Assumes the event has already been removed from the queue. 1449 void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { 1450 p.mHandled = handled; 1451 if (p.mHandler.getLooper().isCurrentThread()) { 1452 // Already running on the callback handler thread so we can send the callback 1453 // immediately. 1454 p.run(); 1455 } else { 1456 // Post the event to the callback handler thread. 1457 // In this case, the callback will be responsible for recycling the event. 1458 Message msg = Message.obtain(p.mHandler, p); 1459 msg.setAsynchronous(true); 1460 msg.sendToTarget(); 1461 } 1462 } 1463 1464 private void flushPendingEventsLocked() { 1465 mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); 1466 1467 final int count = mPendingEvents.size(); 1468 for (int i = 0; i < count; i++) { 1469 int seq = mPendingEvents.keyAt(i); 1470 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0); 1471 msg.setAsynchronous(true); 1472 msg.sendToTarget(); 1473 } 1474 } 1475 1476 private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, 1477 FinishedInputEventCallback callback, Handler handler) { 1478 PendingEvent p = mPendingEventPool.acquire(); 1479 if (p == null) { 1480 p = new PendingEvent(); 1481 } 1482 p.mEvent = event; 1483 p.mToken = token; 1484 p.mCallback = callback; 1485 p.mHandler = handler; 1486 return p; 1487 } 1488 1489 private void recyclePendingEventLocked(PendingEvent p) { 1490 p.recycle(); 1491 mPendingEventPool.release(p); 1492 } 1493 1494 IBinder getToken() { 1495 return mToken; 1496 } 1497 1498 private void releaseInternal() { 1499 mToken = null; 1500 synchronized (mHandler) { 1501 if (mChannel != null) { 1502 if (mSender != null) { 1503 flushPendingEventsLocked(); 1504 mSender.dispose(); 1505 mSender = null; 1506 } 1507 mChannel.dispose(); 1508 mChannel = null; 1509 } 1510 } 1511 synchronized (mSessionCallbackRecordMap) { 1512 mSessionCallbackRecordMap.remove(mSeq); 1513 } 1514 } 1515 1516 private final class InputEventHandler extends Handler { 1517 public static final int MSG_SEND_INPUT_EVENT = 1; 1518 public static final int MSG_TIMEOUT_INPUT_EVENT = 2; 1519 public static final int MSG_FLUSH_INPUT_EVENT = 3; 1520 1521 InputEventHandler(Looper looper) { 1522 super(looper, null, true); 1523 } 1524 1525 @Override 1526 public void handleMessage(Message msg) { 1527 switch (msg.what) { 1528 case MSG_SEND_INPUT_EVENT: { 1529 sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj); 1530 return; 1531 } 1532 case MSG_TIMEOUT_INPUT_EVENT: { 1533 finishedInputEvent(msg.arg1, false, true); 1534 return; 1535 } 1536 case MSG_FLUSH_INPUT_EVENT: { 1537 finishedInputEvent(msg.arg1, false, false); 1538 return; 1539 } 1540 } 1541 } 1542 } 1543 1544 private final class TvInputEventSender extends InputEventSender { 1545 public TvInputEventSender(InputChannel inputChannel, Looper looper) { 1546 super(inputChannel, looper); 1547 } 1548 1549 @Override 1550 public void onInputEventFinished(int seq, boolean handled) { 1551 finishedInputEvent(seq, handled, false); 1552 } 1553 } 1554 1555 private final class PendingEvent implements Runnable { 1556 public InputEvent mEvent; 1557 public Object mToken; 1558 public FinishedInputEventCallback mCallback; 1559 public Handler mHandler; 1560 public boolean mHandled; 1561 1562 public void recycle() { 1563 mEvent = null; 1564 mToken = null; 1565 mCallback = null; 1566 mHandler = null; 1567 mHandled = false; 1568 } 1569 1570 @Override 1571 public void run() { 1572 mCallback.onFinishedInputEvent(mToken, mHandled); 1573 1574 synchronized (mHandler) { 1575 recyclePendingEventLocked(this); 1576 } 1577 } 1578 } 1579 } 1580} 1581