Home | History | Annotate | Download | only in tv
      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 
     17 package android.media.tv;
     18 
     19 import android.annotation.FloatRange;
     20 import android.annotation.MainThread;
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.annotation.SuppressLint;
     24 import android.annotation.SystemApi;
     25 import android.app.ActivityManager;
     26 import android.app.Service;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.graphics.PixelFormat;
     30 import android.graphics.Rect;
     31 import android.hardware.hdmi.HdmiDeviceInfo;
     32 import android.media.PlaybackParams;
     33 import android.net.Uri;
     34 import android.os.AsyncTask;
     35 import android.os.Bundle;
     36 import android.os.Handler;
     37 import android.os.IBinder;
     38 import android.os.Message;
     39 import android.os.Process;
     40 import android.os.RemoteCallbackList;
     41 import android.os.RemoteException;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 import android.view.Gravity;
     45 import android.view.InputChannel;
     46 import android.view.InputDevice;
     47 import android.view.InputEvent;
     48 import android.view.InputEventReceiver;
     49 import android.view.KeyEvent;
     50 import android.view.MotionEvent;
     51 import android.view.Surface;
     52 import android.view.View;
     53 import android.view.WindowManager;
     54 import android.view.accessibility.CaptioningManager;
     55 import android.widget.FrameLayout;
     56 
     57 import com.android.internal.os.SomeArgs;
     58 import com.android.internal.util.Preconditions;
     59 
     60 import java.util.ArrayList;
     61 import java.util.HashSet;
     62 import java.util.List;
     63 import java.util.Set;
     64 
     65 /**
     66  * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which
     67  * provides pass-through video or broadcast TV programs.
     68  *
     69  * <p>Applications will not normally use this service themselves, instead relying on the standard
     70  * interaction provided by {@link TvView}. Those implementing TV input services should normally do
     71  * so by deriving from this class and providing their own session implementation based on
     72  * {@link TvInputService.Session}. All TV input services must require that clients hold the
     73  * {@link android.Manifest.permission#BIND_TV_INPUT} in order to interact with the service; if this
     74  * permission is not specified in the manifest, the system will refuse to bind to that TV input
     75  * service.
     76  */
     77 public abstract class TvInputService extends Service {
     78     private static final boolean DEBUG = false;
     79     private static final String TAG = "TvInputService";
     80 
     81     private static final int DETACH_OVERLAY_VIEW_TIMEOUT_MS = 5000;
     82 
     83     /**
     84      * This is the interface name that a service implementing a TV input should say that it support
     85      * -- that is, this is the action it uses for its intent filter. To be supported, the service
     86      * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
     87      * other applications cannot abuse it.
     88      */
     89     public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
     90 
     91     /**
     92      * Name under which a TvInputService component publishes information about itself.
     93      * This meta-data must reference an XML resource containing an
     94      * <code>&lt;{@link android.R.styleable#TvInputService tv-input}&gt;</code>
     95      * tag.
     96      */
     97     public static final String SERVICE_META_DATA = "android.media.tv.input";
     98 
     99     /**
    100      * Handler instance to handle request from TV Input Manager Service. Should be run in the main
    101      * looper to be synchronously run with {@code Session.mHandler}.
    102      */
    103     private final Handler mServiceHandler = new ServiceHandler();
    104     private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
    105             new RemoteCallbackList<>();
    106 
    107     private TvInputManager mTvInputManager;
    108 
    109     @Override
    110     public final IBinder onBind(Intent intent) {
    111         return new ITvInputService.Stub() {
    112             @Override
    113             public void registerCallback(ITvInputServiceCallback cb) {
    114                 if (cb != null) {
    115                     mCallbacks.register(cb);
    116                 }
    117             }
    118 
    119             @Override
    120             public void unregisterCallback(ITvInputServiceCallback cb) {
    121                 if (cb != null) {
    122                     mCallbacks.unregister(cb);
    123                 }
    124             }
    125 
    126             @Override
    127             public void createSession(InputChannel channel, ITvInputSessionCallback cb,
    128                     String inputId) {
    129                 if (channel == null) {
    130                     Log.w(TAG, "Creating session without input channel");
    131                 }
    132                 if (cb == null) {
    133                     return;
    134                 }
    135                 SomeArgs args = SomeArgs.obtain();
    136                 args.arg1 = channel;
    137                 args.arg2 = cb;
    138                 args.arg3 = inputId;
    139                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
    140             }
    141 
    142             @Override
    143             public void createRecordingSession(ITvInputSessionCallback cb, String inputId) {
    144                 if (cb == null) {
    145                     return;
    146                 }
    147                 SomeArgs args = SomeArgs.obtain();
    148                 args.arg1 = cb;
    149                 args.arg2 = inputId;
    150                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args)
    151                         .sendToTarget();
    152             }
    153 
    154             @Override
    155             public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
    156                 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_INPUT,
    157                         hardwareInfo).sendToTarget();
    158             }
    159 
    160             @Override
    161             public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
    162                 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_INPUT,
    163                         hardwareInfo).sendToTarget();
    164             }
    165 
    166             @Override
    167             public void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
    168                 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_INPUT,
    169                         deviceInfo).sendToTarget();
    170             }
    171 
    172             @Override
    173             public void notifyHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
    174                 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_INPUT,
    175                         deviceInfo).sendToTarget();
    176             }
    177         };
    178     }
    179 
    180     /**
    181      * Returns a concrete implementation of {@link Session}.
    182      *
    183      * <p>May return {@code null} if this TV input service fails to create a session for some
    184      * reason. If TV input represents an external device connected to a hardware TV input,
    185      * {@link HardwareSession} should be returned.
    186      *
    187      * @param inputId The ID of the TV input associated with the session.
    188      */
    189     @Nullable
    190     public abstract Session onCreateSession(String inputId);
    191 
    192     /**
    193      * Returns a concrete implementation of {@link RecordingSession}.
    194      *
    195      * <p>May return {@code null} if this TV input service fails to create a recording session for
    196      * some reason.
    197      *
    198      * @param inputId The ID of the TV input associated with the recording session.
    199      */
    200     @Nullable
    201     public RecordingSession onCreateRecordingSession(String inputId) {
    202         return null;
    203     }
    204 
    205     /**
    206      * Returns a new {@link TvInputInfo} object if this service is responsible for
    207      * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
    208      * ignoring all hardware input.
    209      *
    210      * @param hardwareInfo {@link TvInputHardwareInfo} object just added.
    211      * @hide
    212      */
    213     @Nullable
    214     @SystemApi
    215     public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
    216         return null;
    217     }
    218 
    219     /**
    220      * Returns the input ID for {@code deviceId} if it is handled by this service;
    221      * otherwise, return {@code null}. Override to modify default behavior of ignoring all hardware
    222      * input.
    223      *
    224      * @param hardwareInfo {@link TvInputHardwareInfo} object just removed.
    225      * @hide
    226      */
    227     @Nullable
    228     @SystemApi
    229     public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
    230         return null;
    231     }
    232 
    233     /**
    234      * Returns a new {@link TvInputInfo} object if this service is responsible for
    235      * {@code deviceInfo}; otherwise, return {@code null}. Override to modify default behavior of
    236      * ignoring all HDMI logical input device.
    237      *
    238      * @param deviceInfo {@link HdmiDeviceInfo} object just added.
    239      * @hide
    240      */
    241     @Nullable
    242     @SystemApi
    243     public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
    244         return null;
    245     }
    246 
    247     /**
    248      * Returns the input ID for {@code deviceInfo} if it is handled by this service; otherwise,
    249      * return {@code null}. Override to modify default behavior of ignoring all HDMI logical input
    250      * device.
    251      *
    252      * @param deviceInfo {@link HdmiDeviceInfo} object just removed.
    253      * @hide
    254      */
    255     @Nullable
    256     @SystemApi
    257     public String onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
    258         return null;
    259     }
    260 
    261     private boolean isPassthroughInput(String inputId) {
    262         if (mTvInputManager == null) {
    263             mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
    264         }
    265         TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
    266         return info != null && info.isPassthroughInput();
    267     }
    268 
    269     /**
    270      * Base class for derived classes to implement to provide a TV input session.
    271      */
    272     public abstract static class Session implements KeyEvent.Callback {
    273         private static final int POSITION_UPDATE_INTERVAL_MS = 1000;
    274 
    275         private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
    276         private final WindowManager mWindowManager;
    277         final Handler mHandler;
    278         private WindowManager.LayoutParams mWindowParams;
    279         private Surface mSurface;
    280         private final Context mContext;
    281         private FrameLayout mOverlayViewContainer;
    282         private View mOverlayView;
    283         private OverlayViewCleanUpTask mOverlayViewCleanUpTask;
    284         private boolean mOverlayViewEnabled;
    285         private IBinder mWindowToken;
    286         private Rect mOverlayFrame;
    287         private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
    288         private long mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
    289         private final TimeShiftPositionTrackingRunnable
    290                 mTimeShiftPositionTrackingRunnable = new TimeShiftPositionTrackingRunnable();
    291 
    292         private final Object mLock = new Object();
    293         // @GuardedBy("mLock")
    294         private ITvInputSessionCallback mSessionCallback;
    295         // @GuardedBy("mLock")
    296         private final List<Runnable> mPendingActions = new ArrayList<>();
    297 
    298         /**
    299          * Creates a new Session.
    300          *
    301          * @param context The context of the application
    302          */
    303         public Session(Context context) {
    304             mContext = context;
    305             mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    306             mHandler = new Handler(context.getMainLooper());
    307         }
    308 
    309         /**
    310          * Enables or disables the overlay view.
    311          *
    312          * <p>By default, the overlay view is disabled. Must be called explicitly after the
    313          * session is created to enable the overlay view.
    314          *
    315          * <p>The TV input service can disable its overlay view when the size of the overlay view is
    316          * insufficient to display the whole information, such as when used in Picture-in-picture.
    317          * Override {@link #onOverlayViewSizeChanged} to get the size of the overlay view, which
    318          * then can be used to determine whether to enable/disable the overlay view.
    319          *
    320          * @param enable {@code true} if you want to enable the overlay view. {@code false}
    321          *            otherwise.
    322          */
    323         public void setOverlayViewEnabled(final boolean enable) {
    324             mHandler.post(new Runnable() {
    325                 @Override
    326                 public void run() {
    327                     if (enable == mOverlayViewEnabled) {
    328                         return;
    329                     }
    330                     mOverlayViewEnabled = enable;
    331                     if (enable) {
    332                         if (mWindowToken != null) {
    333                             createOverlayView(mWindowToken, mOverlayFrame);
    334                         }
    335                     } else {
    336                         removeOverlayView(false);
    337                     }
    338                 }
    339             });
    340         }
    341 
    342         /**
    343          * Dispatches an event to the application using this session.
    344          *
    345          * @param eventType The type of the event.
    346          * @param eventArgs Optional arguments of the event.
    347          * @hide
    348          */
    349         @SystemApi
    350         public void notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs) {
    351             Preconditions.checkNotNull(eventType);
    352             executeOrPostRunnableOnMainThread(new Runnable() {
    353                 @Override
    354                 public void run() {
    355                     try {
    356                         if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")");
    357                         if (mSessionCallback != null) {
    358                             mSessionCallback.onSessionEvent(eventType, eventArgs);
    359                         }
    360                     } catch (RemoteException e) {
    361                         Log.w(TAG, "error in sending event (event=" + eventType + ")", e);
    362                     }
    363                 }
    364             });
    365         }
    366 
    367         /**
    368          * Informs the application that the current channel is re-tuned for some reason and the
    369          * session now displays the content from a new channel. This is used to handle special cases
    370          * such as when the current channel becomes unavailable, it is necessary to send the user to
    371          * a certain channel or the user changes channel in some other way (e.g. by using a
    372          * dedicated remote).
    373          *
    374          * @param channelUri The URI of the new channel.
    375          */
    376         public void notifyChannelRetuned(final Uri channelUri) {
    377             executeOrPostRunnableOnMainThread(new Runnable() {
    378                 @MainThread
    379                 @Override
    380                 public void run() {
    381                     try {
    382                         if (DEBUG) Log.d(TAG, "notifyChannelRetuned");
    383                         if (mSessionCallback != null) {
    384                             mSessionCallback.onChannelRetuned(channelUri);
    385                         }
    386                     } catch (RemoteException e) {
    387                         Log.w(TAG, "error in notifyChannelRetuned", e);
    388                     }
    389                 }
    390             });
    391         }
    392 
    393         /**
    394          * Sends the list of all audio/video/subtitle tracks. The is used by the framework to
    395          * maintain the track information for a given session, which in turn is used by
    396          * {@link TvView#getTracks} for the application to retrieve metadata for a given track type.
    397          * The TV input service must call this method as soon as the track information becomes
    398          * available or is updated. Note that in a case where a part of the information for a
    399          * certain track is updated, it is not necessary to create a new {@link TvTrackInfo} object
    400          * with a different track ID.
    401          *
    402          * @param tracks A list which includes track information.
    403          */
    404         public void notifyTracksChanged(final List<TvTrackInfo> tracks) {
    405             final List<TvTrackInfo> tracksCopy = new ArrayList<>(tracks);
    406             executeOrPostRunnableOnMainThread(new Runnable() {
    407                 @MainThread
    408                 @Override
    409                 public void run() {
    410                     try {
    411                         if (DEBUG) Log.d(TAG, "notifyTracksChanged");
    412                         if (mSessionCallback != null) {
    413                             mSessionCallback.onTracksChanged(tracksCopy);
    414                         }
    415                     } catch (RemoteException e) {
    416                         Log.w(TAG, "error in notifyTracksChanged", e);
    417                     }
    418                 }
    419             });
    420         }
    421 
    422         /**
    423          * Sends the type and ID of a selected track. This is used to inform the application that a
    424          * specific track is selected. The TV input service must call this method as soon as a track
    425          * is selected either by default or in response to a call to {@link #onSelectTrack}. The
    426          * selected track ID for a given type is maintained in the framework until the next call to
    427          * this method even after the entire track list is updated (but is reset when the session is
    428          * tuned to a new channel), so care must be taken not to result in an obsolete track ID.
    429          *
    430          * @param type The type of the selected track. The type can be
    431          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
    432          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
    433          * @param trackId The ID of the selected track.
    434          * @see #onSelectTrack
    435          */
    436         public void notifyTrackSelected(final int type, final String trackId) {
    437             executeOrPostRunnableOnMainThread(new Runnable() {
    438                 @MainThread
    439                 @Override
    440                 public void run() {
    441                     try {
    442                         if (DEBUG) Log.d(TAG, "notifyTrackSelected");
    443                         if (mSessionCallback != null) {
    444                             mSessionCallback.onTrackSelected(type, trackId);
    445                         }
    446                     } catch (RemoteException e) {
    447                         Log.w(TAG, "error in notifyTrackSelected", e);
    448                     }
    449                 }
    450             });
    451         }
    452 
    453         /**
    454          * Informs the application that the video is now available for watching. Video is blocked
    455          * until this method is called.
    456          *
    457          * <p>The TV input service must call this method as soon as the content rendered onto its
    458          * surface is ready for viewing. This method must be called each time {@link #onTune}
    459          * is called.
    460          *
    461          * @see #notifyVideoUnavailable
    462          */
    463         public void notifyVideoAvailable() {
    464             executeOrPostRunnableOnMainThread(new Runnable() {
    465                 @MainThread
    466                 @Override
    467                 public void run() {
    468                     try {
    469                         if (DEBUG) Log.d(TAG, "notifyVideoAvailable");
    470                         if (mSessionCallback != null) {
    471                             mSessionCallback.onVideoAvailable();
    472                         }
    473                     } catch (RemoteException e) {
    474                         Log.w(TAG, "error in notifyVideoAvailable", e);
    475                     }
    476                 }
    477             });
    478         }
    479 
    480         /**
    481          * Informs the application that the video became unavailable for some reason. This is
    482          * primarily used to signal the application to block the screen not to show any intermittent
    483          * video artifacts.
    484          *
    485          * @param reason The reason why the video became unavailable:
    486          *            <ul>
    487          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
    488          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
    489          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
    490          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
    491          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
    492          *            </ul>
    493          * @see #notifyVideoAvailable
    494          */
    495         public void notifyVideoUnavailable(
    496                 @TvInputManager.VideoUnavailableReason final int reason) {
    497             if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START
    498                     || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
    499                 Log.e(TAG, "notifyVideoUnavailable - unknown reason: " + reason);
    500             }
    501             executeOrPostRunnableOnMainThread(new Runnable() {
    502                 @MainThread
    503                 @Override
    504                 public void run() {
    505                     try {
    506                         if (DEBUG) Log.d(TAG, "notifyVideoUnavailable");
    507                         if (mSessionCallback != null) {
    508                             mSessionCallback.onVideoUnavailable(reason);
    509                         }
    510                     } catch (RemoteException e) {
    511                         Log.w(TAG, "error in notifyVideoUnavailable", e);
    512                     }
    513                 }
    514             });
    515         }
    516 
    517         /**
    518          * Informs the application that the user is allowed to watch the current program content.
    519          *
    520          * <p>Each TV input service is required to query the system whether the user is allowed to
    521          * watch the current program before showing it to the user if the parental controls is
    522          * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
    523          * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
    524          * service should block the content or not is determined by invoking
    525          * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
    526          * with the content rating for the current program. Then the {@link TvInputManager} makes a
    527          * judgment based on the user blocked ratings stored in the secure settings and returns the
    528          * result. If the rating in question turns out to be allowed by the user, the TV input
    529          * service must call this method to notify the application that is permitted to show the
    530          * content.
    531          *
    532          * <p>Each TV input service also needs to continuously listen to any changes made to the
    533          * parental controls settings by registering a broadcast receiver to receive
    534          * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
    535          * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
    536          * reevaluate the current program with the new parental controls settings.
    537          *
    538          * @see #notifyContentBlocked
    539          * @see TvInputManager
    540          */
    541         public void notifyContentAllowed() {
    542             executeOrPostRunnableOnMainThread(new Runnable() {
    543                 @MainThread
    544                 @Override
    545                 public void run() {
    546                     try {
    547                         if (DEBUG) Log.d(TAG, "notifyContentAllowed");
    548                         if (mSessionCallback != null) {
    549                             mSessionCallback.onContentAllowed();
    550                         }
    551                     } catch (RemoteException e) {
    552                         Log.w(TAG, "error in notifyContentAllowed", e);
    553                     }
    554                 }
    555             });
    556         }
    557 
    558         /**
    559          * Informs the application that the current program content is blocked by parent controls.
    560          *
    561          * <p>Each TV input service is required to query the system whether the user is allowed to
    562          * watch the current program before showing it to the user if the parental controls is
    563          * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
    564          * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
    565          * service should block the content or not is determined by invoking
    566          * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
    567          * with the content rating for the current program or {@link TvContentRating#UNRATED} in
    568          * case the rating information is missing. Then the {@link TvInputManager} makes a judgment
    569          * based on the user blocked ratings stored in the secure settings and returns the result.
    570          * If the rating in question turns out to be blocked, the TV input service must immediately
    571          * block the content and call this method with the content rating of the current program to
    572          * prompt the PIN verification screen.
    573          *
    574          * <p>Each TV input service also needs to continuously listen to any changes made to the
    575          * parental controls settings by registering a broadcast receiver to receive
    576          * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
    577          * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
    578          * reevaluate the current program with the new parental controls settings.
    579          *
    580          * @param rating The content rating for the current TV program. Can be
    581          *            {@link TvContentRating#UNRATED}.
    582          * @see #notifyContentAllowed
    583          * @see TvInputManager
    584          */
    585         public void notifyContentBlocked(@NonNull final TvContentRating rating) {
    586             Preconditions.checkNotNull(rating);
    587             executeOrPostRunnableOnMainThread(new Runnable() {
    588                 @MainThread
    589                 @Override
    590                 public void run() {
    591                     try {
    592                         if (DEBUG) Log.d(TAG, "notifyContentBlocked");
    593                         if (mSessionCallback != null) {
    594                             mSessionCallback.onContentBlocked(rating.flattenToString());
    595                         }
    596                     } catch (RemoteException e) {
    597                         Log.w(TAG, "error in notifyContentBlocked", e);
    598                     }
    599                 }
    600             });
    601         }
    602 
    603         /**
    604          * Informs the application that the time shift status is changed.
    605          *
    606          * <p>Prior to calling this method, the application assumes the status
    607          * {@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN}. Right after the session is created, it
    608          * is important to invoke the method with the status
    609          * {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} if the implementation does support
    610          * time shifting, or {@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} otherwise. Failure
    611          * to notifying the current status change immediately might result in an undesirable
    612          * behavior in the application such as hiding the play controls.
    613          *
    614          * <p>If the status {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} is reported, the
    615          * application assumes it can pause/resume playback, seek to a specified time position and
    616          * set playback rate and audio mode. The implementation should override
    617          * {@link #onTimeShiftPause}, {@link #onTimeShiftResume}, {@link #onTimeShiftSeekTo},
    618          * {@link #onTimeShiftGetStartPosition}, {@link #onTimeShiftGetCurrentPosition} and
    619          * {@link #onTimeShiftSetPlaybackParams}.
    620          *
    621          * @param status The current time shift status. Should be one of the followings.
    622          * <ul>
    623          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
    624          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
    625          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
    626          * </ul>
    627          */
    628         public void notifyTimeShiftStatusChanged(@TvInputManager.TimeShiftStatus final int status) {
    629             executeOrPostRunnableOnMainThread(new Runnable() {
    630                 @MainThread
    631                 @Override
    632                 public void run() {
    633                     timeShiftEnablePositionTracking(
    634                             status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE);
    635                     try {
    636                         if (DEBUG) Log.d(TAG, "notifyTimeShiftStatusChanged");
    637                         if (mSessionCallback != null) {
    638                             mSessionCallback.onTimeShiftStatusChanged(status);
    639                         }
    640                     } catch (RemoteException e) {
    641                         Log.w(TAG, "error in notifyTimeShiftStatusChanged", e);
    642                     }
    643                 }
    644             });
    645         }
    646 
    647         private void notifyTimeShiftStartPositionChanged(final long timeMs) {
    648             executeOrPostRunnableOnMainThread(new Runnable() {
    649                 @MainThread
    650                 @Override
    651                 public void run() {
    652                     try {
    653                         if (DEBUG) Log.d(TAG, "notifyTimeShiftStartPositionChanged");
    654                         if (mSessionCallback != null) {
    655                             mSessionCallback.onTimeShiftStartPositionChanged(timeMs);
    656                         }
    657                     } catch (RemoteException e) {
    658                         Log.w(TAG, "error in notifyTimeShiftStartPositionChanged", e);
    659                     }
    660                 }
    661             });
    662         }
    663 
    664         private void notifyTimeShiftCurrentPositionChanged(final long timeMs) {
    665             executeOrPostRunnableOnMainThread(new Runnable() {
    666                 @MainThread
    667                 @Override
    668                 public void run() {
    669                     try {
    670                         if (DEBUG) Log.d(TAG, "notifyTimeShiftCurrentPositionChanged");
    671                         if (mSessionCallback != null) {
    672                             mSessionCallback.onTimeShiftCurrentPositionChanged(timeMs);
    673                         }
    674                     } catch (RemoteException e) {
    675                         Log.w(TAG, "error in notifyTimeShiftCurrentPositionChanged", e);
    676                     }
    677                 }
    678             });
    679         }
    680 
    681         /**
    682          * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
    683          * is relative to the overlay view that sits on top of this surface.
    684          *
    685          * @param left Left position in pixels, relative to the overlay view.
    686          * @param top Top position in pixels, relative to the overlay view.
    687          * @param right Right position in pixels, relative to the overlay view.
    688          * @param bottom Bottom position in pixels, relative to the overlay view.
    689          * @see #onOverlayViewSizeChanged
    690          */
    691         public void layoutSurface(final int left, final int top, final int right,
    692                 final int bottom) {
    693             if (left > right || top > bottom) {
    694                 throw new IllegalArgumentException("Invalid parameter");
    695             }
    696             executeOrPostRunnableOnMainThread(new Runnable() {
    697                 @MainThread
    698                 @Override
    699                 public void run() {
    700                     try {
    701                         if (DEBUG) Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top + ", r="
    702                                 + right + ", b=" + bottom + ",)");
    703                         if (mSessionCallback != null) {
    704                             mSessionCallback.onLayoutSurface(left, top, right, bottom);
    705                         }
    706                     } catch (RemoteException e) {
    707                         Log.w(TAG, "error in layoutSurface", e);
    708                     }
    709                 }
    710             });
    711         }
    712 
    713         /**
    714          * Called when the session is released.
    715          */
    716         public abstract void onRelease();
    717 
    718         /**
    719          * Sets the current session as the main session. The main session is a session whose
    720          * corresponding TV input determines the HDMI-CEC active source device.
    721          *
    722          * <p>TV input service that manages HDMI-CEC logical device should implement {@link
    723          * #onSetMain} to (1) select the corresponding HDMI logical device as the source device
    724          * when {@code isMain} is {@code true}, and to (2) select the internal device (= TV itself)
    725          * as the source device when {@code isMain} is {@code false} and the session is still main.
    726          * Also, if a surface is passed to a non-main session and active source is changed to
    727          * initiate the surface, the active source should be returned to the main session.
    728          *
    729          * <p>{@link TvView} guarantees that, when tuning involves a session transition, {@code
    730          * onSetMain(true)} for new session is called first, {@code onSetMain(false)} for old
    731          * session is called afterwards. This allows {@code onSetMain(false)} to be no-op when TV
    732          * input service knows that the next main session corresponds to another HDMI logical
    733          * device. Practically, this implies that one TV input service should handle all HDMI port
    734          * and HDMI-CEC logical devices for smooth active source transition.
    735          *
    736          * @param isMain If true, session should become main.
    737          * @see TvView#setMain
    738          * @hide
    739          */
    740         @SystemApi
    741         public void onSetMain(boolean isMain) {
    742         }
    743 
    744         /**
    745          * Called when the application sets the surface.
    746          *
    747          * <p>The TV input service should render video onto the given surface. When called with
    748          * {@code null}, the input service should immediately free any references to the
    749          * currently set surface and stop using it.
    750          *
    751          * @param surface The surface to be used for video rendering. Can be {@code null}.
    752          * @return {@code true} if the surface was set successfully, {@code false} otherwise.
    753          */
    754         public abstract boolean onSetSurface(@Nullable Surface surface);
    755 
    756         /**
    757          * Called after any structural changes (format or size) have been made to the surface passed
    758          * in {@link #onSetSurface}. This method is always called at least once, after
    759          * {@link #onSetSurface} is called with non-null surface.
    760          *
    761          * @param format The new PixelFormat of the surface.
    762          * @param width The new width of the surface.
    763          * @param height The new height of the surface.
    764          */
    765         public void onSurfaceChanged(int format, int width, int height) {
    766         }
    767 
    768         /**
    769          * Called when the size of the overlay view is changed by the application.
    770          *
    771          * <p>This is always called at least once when the session is created regardless of whether
    772          * the overlay view is enabled or not. The overlay view size is the same as the containing
    773          * {@link TvView}. Note that the size of the underlying surface can be different if the
    774          * surface was changed by calling {@link #layoutSurface}.
    775          *
    776          * @param width The width of the overlay view.
    777          * @param height The height of the overlay view.
    778          */
    779         public void onOverlayViewSizeChanged(int width, int height) {
    780         }
    781 
    782         /**
    783          * Sets the relative stream volume of the current TV input session.
    784          *
    785          * <p>The implementation should honor this request in order to handle audio focus changes or
    786          * mute the current session when multiple sessions, possibly from different inputs are
    787          * active. If the method has not yet been called, the implementation should assume the
    788          * default value of {@code 1.0f}.
    789          *
    790          * @param volume A volume value between {@code 0.0f} to {@code 1.0f}.
    791          */
    792         public abstract void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume);
    793 
    794         /**
    795          * Tunes to a given channel.
    796          *
    797          * <p>No video will be displayed until {@link #notifyVideoAvailable()} is called.
    798          * Also, {@link #notifyVideoUnavailable(int)} should be called when the TV input cannot
    799          * continue playing the given channel.
    800          *
    801          * @param channelUri The URI of the channel.
    802          * @return {@code true} if the tuning was successful, {@code false} otherwise.
    803          */
    804         public abstract boolean onTune(Uri channelUri);
    805 
    806         /**
    807          * Tunes to a given channel. Override this method in order to handle domain-specific
    808          * features that are only known between certain TV inputs and their clients.
    809          *
    810          * <p>The default implementation calls {@link #onTune(Uri)}.
    811          *
    812          * @param channelUri The URI of the channel.
    813          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
    814          *            name, i.e. prefixed with a package name you own, so that different developers
    815          *            will not create conflicting keys.
    816          * @return {@code true} if the tuning was successful, {@code false} otherwise.
    817          */
    818         public boolean onTune(Uri channelUri, Bundle params) {
    819             return onTune(channelUri);
    820         }
    821 
    822         /**
    823          * Enables or disables the caption.
    824          *
    825          * <p>The locale for the user's preferred captioning language can be obtained by calling
    826          * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}.
    827          *
    828          * @param enabled {@code true} to enable, {@code false} to disable.
    829          * @see CaptioningManager
    830          */
    831         public abstract void onSetCaptionEnabled(boolean enabled);
    832 
    833         /**
    834          * Requests to unblock the content according to the given rating.
    835          *
    836          * <p>The implementation should unblock the content.
    837          * TV input service has responsibility to decide when/how the unblock expires
    838          * while it can keep previously unblocked ratings in order not to ask a user
    839          * to unblock whenever a content rating is changed.
    840          * Therefore an unblocked rating can be valid for a channel, a program,
    841          * or certain amount of time depending on the implementation.
    842          *
    843          * @param unblockedRating An unblocked content rating
    844          */
    845         public void onUnblockContent(TvContentRating unblockedRating) {
    846         }
    847 
    848         /**
    849          * Selects a given track.
    850          *
    851          * <p>If this is done successfully, the implementation should call
    852          * {@link #notifyTrackSelected} to help applications maintain the up-to-date list of the
    853          * selected tracks.
    854          *
    855          * @param trackId The ID of the track to select. {@code null} means to unselect the current
    856          *            track for a given type.
    857          * @param type The type of the track to select. The type can be
    858          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
    859          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
    860          * @return {@code true} if the track selection was successful, {@code false} otherwise.
    861          * @see #notifyTrackSelected
    862          */
    863         public boolean onSelectTrack(int type, @Nullable String trackId) {
    864             return false;
    865         }
    866 
    867         /**
    868          * Processes a private command sent from the application to the TV input. This can be used
    869          * to provide domain-specific features that are only known between certain TV inputs and
    870          * their clients.
    871          *
    872          * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
    873          *            i.e. prefixed with a package name you own, so that different developers will
    874          *            not create conflicting commands.
    875          * @param data Any data to include with the command.
    876          */
    877         public void onAppPrivateCommand(@NonNull String action, Bundle data) {
    878         }
    879 
    880         /**
    881          * Called when the application requests to create an overlay view. Each session
    882          * implementation can override this method and return its own view.
    883          *
    884          * @return a view attached to the overlay window
    885          */
    886         public View onCreateOverlayView() {
    887             return null;
    888         }
    889 
    890         /**
    891          * Called when the application requests to play a given recorded TV program.
    892          *
    893          * @param recordedProgramUri The URI of a recorded TV program.
    894          * @see #onTimeShiftResume()
    895          * @see #onTimeShiftPause()
    896          * @see #onTimeShiftSeekTo(long)
    897          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
    898          * @see #onTimeShiftGetStartPosition()
    899          * @see #onTimeShiftGetCurrentPosition()
    900          */
    901         public void onTimeShiftPlay(Uri recordedProgramUri) {
    902         }
    903 
    904         /**
    905          * Called when the application requests to pause playback.
    906          *
    907          * @see #onTimeShiftPlay(Uri)
    908          * @see #onTimeShiftResume()
    909          * @see #onTimeShiftSeekTo(long)
    910          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
    911          * @see #onTimeShiftGetStartPosition()
    912          * @see #onTimeShiftGetCurrentPosition()
    913          */
    914         public void onTimeShiftPause() {
    915         }
    916 
    917         /**
    918          * Called when the application requests to resume playback.
    919          *
    920          * @see #onTimeShiftPlay(Uri)
    921          * @see #onTimeShiftPause()
    922          * @see #onTimeShiftSeekTo(long)
    923          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
    924          * @see #onTimeShiftGetStartPosition()
    925          * @see #onTimeShiftGetCurrentPosition()
    926          */
    927         public void onTimeShiftResume() {
    928         }
    929 
    930         /**
    931          * Called when the application requests to seek to a specified time position. Normally, the
    932          * position is given within range between the start and the current time, inclusively. The
    933          * implementation is expected to seek to the nearest time position if the given position is
    934          * not in the range.
    935          *
    936          * @param timeMs The time position to seek to, in milliseconds since the epoch.
    937          * @see #onTimeShiftPlay(Uri)
    938          * @see #onTimeShiftResume()
    939          * @see #onTimeShiftPause()
    940          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
    941          * @see #onTimeShiftGetStartPosition()
    942          * @see #onTimeShiftGetCurrentPosition()
    943          */
    944         public void onTimeShiftSeekTo(long timeMs) {
    945         }
    946 
    947         /**
    948          * Called when the application sets playback parameters containing the speed and audio mode.
    949          *
    950          * <p>Once the playback parameters are set, the implementation should honor the current
    951          * settings until the next tune request. Pause/resume/seek request does not reset the
    952          * parameters previously set.
    953          *
    954          * @param params The playback params.
    955          * @see #onTimeShiftPlay(Uri)
    956          * @see #onTimeShiftResume()
    957          * @see #onTimeShiftPause()
    958          * @see #onTimeShiftSeekTo(long)
    959          * @see #onTimeShiftGetStartPosition()
    960          * @see #onTimeShiftGetCurrentPosition()
    961          */
    962         public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
    963         }
    964 
    965         /**
    966          * Returns the start position for time shifting, in milliseconds since the epoch.
    967          * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the
    968          * moment.
    969          *
    970          * <p>The start position for time shifting indicates the earliest possible time the user can
    971          * seek to. Initially this is equivalent to the time when the implementation starts
    972          * recording. Later it may be adjusted because there is insufficient space or the duration
    973          * of recording is limited by the implementation. The application does not allow the user to
    974          * seek to a position earlier than the start position.
    975          *
    976          * <p>For playback of a recorded program initiated by {@link #onTimeShiftPlay(Uri)}, the
    977          * start position should be 0 and does not change.
    978          *
    979          * @see #onTimeShiftPlay(Uri)
    980          * @see #onTimeShiftResume()
    981          * @see #onTimeShiftPause()
    982          * @see #onTimeShiftSeekTo(long)
    983          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
    984          * @see #onTimeShiftGetCurrentPosition()
    985          */
    986         public long onTimeShiftGetStartPosition() {
    987             return TvInputManager.TIME_SHIFT_INVALID_TIME;
    988         }
    989 
    990         /**
    991          * Returns the current position for time shifting, in milliseconds since the epoch.
    992          * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the
    993          * moment.
    994          *
    995          * <p>The current position for time shifting is the same as the current position of
    996          * playback. It should be equal to or greater than the start position reported by
    997          * {@link #onTimeShiftGetStartPosition()}. When playback is completed, the current position
    998          * should stay where the playback ends, in other words, the returned value of this mehtod
    999          * should be equal to the start position plus the duration of the program.
   1000          *
   1001          * @see #onTimeShiftPlay(Uri)
   1002          * @see #onTimeShiftResume()
   1003          * @see #onTimeShiftPause()
   1004          * @see #onTimeShiftSeekTo(long)
   1005          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
   1006          * @see #onTimeShiftGetStartPosition()
   1007          */
   1008         public long onTimeShiftGetCurrentPosition() {
   1009             return TvInputManager.TIME_SHIFT_INVALID_TIME;
   1010         }
   1011 
   1012         /**
   1013          * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
   1014          * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
   1015          *
   1016          * <p>Override this to intercept key down events before they are processed by the
   1017          * application. If you return true, the application will not process the event itself. If
   1018          * you return false, the normal application processing will occur as if the TV input had not
   1019          * seen the event at all.
   1020          *
   1021          * @param keyCode The value in event.getKeyCode().
   1022          * @param event Description of the key event.
   1023          * @return If you handled the event, return {@code true}. If you want to allow the event to
   1024          *         be handled by the next receiver, return {@code false}.
   1025          */
   1026         @Override
   1027         public boolean onKeyDown(int keyCode, KeyEvent event) {
   1028             return false;
   1029         }
   1030 
   1031         /**
   1032          * Default implementation of
   1033          * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
   1034          * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
   1035          *
   1036          * <p>Override this to intercept key long press events before they are processed by the
   1037          * application. If you return true, the application will not process the event itself. If
   1038          * you return false, the normal application processing will occur as if the TV input had not
   1039          * seen the event at all.
   1040          *
   1041          * @param keyCode The value in event.getKeyCode().
   1042          * @param event Description of the key event.
   1043          * @return If you handled the event, return {@code true}. If you want to allow the event to
   1044          *         be handled by the next receiver, return {@code false}.
   1045          */
   1046         @Override
   1047         public boolean onKeyLongPress(int keyCode, KeyEvent event) {
   1048             return false;
   1049         }
   1050 
   1051         /**
   1052          * Default implementation of
   1053          * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
   1054          * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
   1055          *
   1056          * <p>Override this to intercept special key multiple events before they are processed by
   1057          * the application. If you return true, the application will not itself process the event.
   1058          * If you return false, the normal application processing will occur as if the TV input had
   1059          * not seen the event at all.
   1060          *
   1061          * @param keyCode The value in event.getKeyCode().
   1062          * @param count The number of times the action was made.
   1063          * @param event Description of the key event.
   1064          * @return If you handled the event, return {@code true}. If you want to allow the event to
   1065          *         be handled by the next receiver, return {@code false}.
   1066          */
   1067         @Override
   1068         public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
   1069             return false;
   1070         }
   1071 
   1072         /**
   1073          * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
   1074          * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
   1075          *
   1076          * <p>Override this to intercept key up events before they are processed by the application.
   1077          * If you return true, the application will not itself process the event. If you return false,
   1078          * the normal application processing will occur as if the TV input had not seen the event at
   1079          * all.
   1080          *
   1081          * @param keyCode The value in event.getKeyCode().
   1082          * @param event Description of the key event.
   1083          * @return If you handled the event, return {@code true}. If you want to allow the event to
   1084          *         be handled by the next receiver, return {@code false}.
   1085          */
   1086         @Override
   1087         public boolean onKeyUp(int keyCode, KeyEvent event) {
   1088             return false;
   1089         }
   1090 
   1091         /**
   1092          * Implement this method to handle touch screen motion events on the current input session.
   1093          *
   1094          * @param event The motion event being received.
   1095          * @return If you handled the event, return {@code true}. If you want to allow the event to
   1096          *         be handled by the next receiver, return {@code false}.
   1097          * @see View#onTouchEvent
   1098          */
   1099         public boolean onTouchEvent(MotionEvent event) {
   1100             return false;
   1101         }
   1102 
   1103         /**
   1104          * Implement this method to handle trackball events on the current input session.
   1105          *
   1106          * @param event The motion event being received.
   1107          * @return If you handled the event, return {@code true}. If you want to allow the event to
   1108          *         be handled by the next receiver, return {@code false}.
   1109          * @see View#onTrackballEvent
   1110          */
   1111         public boolean onTrackballEvent(MotionEvent event) {
   1112             return false;
   1113         }
   1114 
   1115         /**
   1116          * Implement this method to handle generic motion events on the current input session.
   1117          *
   1118          * @param event The motion event being received.
   1119          * @return If you handled the event, return {@code true}. If you want to allow the event to
   1120          *         be handled by the next receiver, return {@code false}.
   1121          * @see View#onGenericMotionEvent
   1122          */
   1123         public boolean onGenericMotionEvent(MotionEvent event) {
   1124             return false;
   1125         }
   1126 
   1127         /**
   1128          * This method is called when the application would like to stop using the current input
   1129          * session.
   1130          */
   1131         void release() {
   1132             onRelease();
   1133             if (mSurface != null) {
   1134                 mSurface.release();
   1135                 mSurface = null;
   1136             }
   1137             synchronized(mLock) {
   1138                 mSessionCallback = null;
   1139                 mPendingActions.clear();
   1140             }
   1141             // Removes the overlay view lastly so that any hanging on the main thread can be handled
   1142             // in {@link #scheduleOverlayViewCleanup}.
   1143             removeOverlayView(true);
   1144             mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
   1145         }
   1146 
   1147         /**
   1148          * Calls {@link #onSetMain}.
   1149          */
   1150         void setMain(boolean isMain) {
   1151             onSetMain(isMain);
   1152         }
   1153 
   1154         /**
   1155          * Calls {@link #onSetSurface}.
   1156          */
   1157         void setSurface(Surface surface) {
   1158             onSetSurface(surface);
   1159             if (mSurface != null) {
   1160                 mSurface.release();
   1161             }
   1162             mSurface = surface;
   1163             // TODO: Handle failure.
   1164         }
   1165 
   1166         /**
   1167          * Calls {@link #onSurfaceChanged}.
   1168          */
   1169         void dispatchSurfaceChanged(int format, int width, int height) {
   1170             if (DEBUG) {
   1171                 Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
   1172                         + ", height=" + height + ")");
   1173             }
   1174             onSurfaceChanged(format, width, height);
   1175         }
   1176 
   1177         /**
   1178          * Calls {@link #onSetStreamVolume}.
   1179          */
   1180         void setStreamVolume(float volume) {
   1181             onSetStreamVolume(volume);
   1182         }
   1183 
   1184         /**
   1185          * Calls {@link #onTune(Uri, Bundle)}.
   1186          */
   1187         void tune(Uri channelUri, Bundle params) {
   1188             mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
   1189             onTune(channelUri, params);
   1190             // TODO: Handle failure.
   1191         }
   1192 
   1193         /**
   1194          * Calls {@link #onSetCaptionEnabled}.
   1195          */
   1196         void setCaptionEnabled(boolean enabled) {
   1197             onSetCaptionEnabled(enabled);
   1198         }
   1199 
   1200         /**
   1201          * Calls {@link #onSelectTrack}.
   1202          */
   1203         void selectTrack(int type, String trackId) {
   1204             onSelectTrack(type, trackId);
   1205         }
   1206 
   1207         /**
   1208          * Calls {@link #onUnblockContent}.
   1209          */
   1210         void unblockContent(String unblockedRating) {
   1211             onUnblockContent(TvContentRating.unflattenFromString(unblockedRating));
   1212             // TODO: Handle failure.
   1213         }
   1214 
   1215         /**
   1216          * Calls {@link #onAppPrivateCommand}.
   1217          */
   1218         void appPrivateCommand(String action, Bundle data) {
   1219             onAppPrivateCommand(action, data);
   1220         }
   1221 
   1222         /**
   1223          * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
   1224          * to the overlay window.
   1225          *
   1226          * @param windowToken A window token of the application.
   1227          * @param frame A position of the overlay view.
   1228          */
   1229         void createOverlayView(IBinder windowToken, Rect frame) {
   1230             if (mOverlayViewContainer != null) {
   1231                 removeOverlayView(false);
   1232             }
   1233             if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
   1234             mWindowToken = windowToken;
   1235             mOverlayFrame = frame;
   1236             onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
   1237             if (!mOverlayViewEnabled) {
   1238                 return;
   1239             }
   1240             mOverlayView = onCreateOverlayView();
   1241             if (mOverlayView == null) {
   1242                 return;
   1243             }
   1244             if (mOverlayViewCleanUpTask != null) {
   1245                 mOverlayViewCleanUpTask.cancel(true);
   1246                 mOverlayViewCleanUpTask = null;
   1247             }
   1248             // Creates a container view to check hanging on the overlay view detaching.
   1249             // Adding/removing the overlay view to/from the container make the view attach/detach
   1250             // logic run on the main thread.
   1251             mOverlayViewContainer = new FrameLayout(mContext.getApplicationContext());
   1252             mOverlayViewContainer.addView(mOverlayView);
   1253             // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
   1254             // an overlay window above the media window but below the application window.
   1255             int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
   1256             // We make the overlay view non-focusable and non-touchable so that
   1257             // the application that owns the window token can decide whether to consume or
   1258             // dispatch the input events.
   1259             int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
   1260                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
   1261                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
   1262             if (ActivityManager.isHighEndGfx()) {
   1263                 flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
   1264             }
   1265             mWindowParams = new WindowManager.LayoutParams(
   1266                     frame.right - frame.left, frame.bottom - frame.top,
   1267                     frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
   1268             mWindowParams.privateFlags |=
   1269                     WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
   1270             mWindowParams.gravity = Gravity.START | Gravity.TOP;
   1271             mWindowParams.token = windowToken;
   1272             mWindowManager.addView(mOverlayViewContainer, mWindowParams);
   1273         }
   1274 
   1275         /**
   1276          * Relayouts the current overlay view.
   1277          *
   1278          * @param frame A new position of the overlay view.
   1279          */
   1280         void relayoutOverlayView(Rect frame) {
   1281             if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
   1282             if (mOverlayFrame == null || mOverlayFrame.width() != frame.width()
   1283                     || mOverlayFrame.height() != frame.height()) {
   1284                 // Note: relayoutOverlayView is called whenever TvView's layout is changed
   1285                 // regardless of setOverlayViewEnabled.
   1286                 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
   1287             }
   1288             mOverlayFrame = frame;
   1289             if (!mOverlayViewEnabled || mOverlayViewContainer == null) {
   1290                 return;
   1291             }
   1292             mWindowParams.x = frame.left;
   1293             mWindowParams.y = frame.top;
   1294             mWindowParams.width = frame.right - frame.left;
   1295             mWindowParams.height = frame.bottom - frame.top;
   1296             mWindowManager.updateViewLayout(mOverlayViewContainer, mWindowParams);
   1297         }
   1298 
   1299         /**
   1300          * Removes the current overlay view.
   1301          */
   1302         void removeOverlayView(boolean clearWindowToken) {
   1303             if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayViewContainer + ")");
   1304             if (clearWindowToken) {
   1305                 mWindowToken = null;
   1306                 mOverlayFrame = null;
   1307             }
   1308             if (mOverlayViewContainer != null) {
   1309                 // Removes the overlay view from the view hierarchy in advance so that it can be
   1310                 // cleaned up in the {@link OverlayViewCleanUpTask} if the remove process is
   1311                 // hanging.
   1312                 mOverlayViewContainer.removeView(mOverlayView);
   1313                 mOverlayView = null;
   1314                 mWindowManager.removeView(mOverlayViewContainer);
   1315                 mOverlayViewContainer = null;
   1316                 mWindowParams = null;
   1317             }
   1318         }
   1319 
   1320         /**
   1321          * Calls {@link #onTimeShiftPlay(Uri)}.
   1322          */
   1323         void timeShiftPlay(Uri recordedProgramUri) {
   1324             mCurrentPositionMs = 0;
   1325             onTimeShiftPlay(recordedProgramUri);
   1326         }
   1327 
   1328         /**
   1329          * Calls {@link #onTimeShiftPause}.
   1330          */
   1331         void timeShiftPause() {
   1332             onTimeShiftPause();
   1333         }
   1334 
   1335         /**
   1336          * Calls {@link #onTimeShiftResume}.
   1337          */
   1338         void timeShiftResume() {
   1339             onTimeShiftResume();
   1340         }
   1341 
   1342         /**
   1343          * Calls {@link #onTimeShiftSeekTo}.
   1344          */
   1345         void timeShiftSeekTo(long timeMs) {
   1346             onTimeShiftSeekTo(timeMs);
   1347         }
   1348 
   1349         /**
   1350          * Calls {@link #onTimeShiftSetPlaybackParams}.
   1351          */
   1352         void timeShiftSetPlaybackParams(PlaybackParams params) {
   1353             onTimeShiftSetPlaybackParams(params);
   1354         }
   1355 
   1356         /**
   1357          * Enable/disable position tracking.
   1358          *
   1359          * @param enable {@code true} to enable tracking, {@code false} otherwise.
   1360          */
   1361         void timeShiftEnablePositionTracking(boolean enable) {
   1362             if (enable) {
   1363                 mHandler.post(mTimeShiftPositionTrackingRunnable);
   1364             } else {
   1365                 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
   1366                 mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
   1367                 mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
   1368             }
   1369         }
   1370 
   1371         /**
   1372          * Schedules a task which checks whether the overlay view is detached and kills the process
   1373          * if it is not. Note that this method is expected to be called in a non-main thread.
   1374          */
   1375         void scheduleOverlayViewCleanup() {
   1376             View overlayViewParent = mOverlayViewContainer;
   1377             if (overlayViewParent != null) {
   1378                 mOverlayViewCleanUpTask = new OverlayViewCleanUpTask();
   1379                 mOverlayViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
   1380                         overlayViewParent);
   1381             }
   1382         }
   1383 
   1384         /**
   1385          * Takes care of dispatching incoming input events and tells whether the event was handled.
   1386          */
   1387         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
   1388             if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
   1389             boolean isNavigationKey = false;
   1390             boolean skipDispatchToOverlayView = false;
   1391             if (event instanceof KeyEvent) {
   1392                 KeyEvent keyEvent = (KeyEvent) event;
   1393                 if (keyEvent.dispatch(this, mDispatcherState, this)) {
   1394                     return TvInputManager.Session.DISPATCH_HANDLED;
   1395                 }
   1396                 isNavigationKey = isNavigationKey(keyEvent.getKeyCode());
   1397                 // When media keys and KEYCODE_MEDIA_AUDIO_TRACK are dispatched to ViewRootImpl,
   1398                 // ViewRootImpl always consumes the keys. In this case, the application loses
   1399                 // a chance to handle media keys. Therefore, media keys are not dispatched to
   1400                 // ViewRootImpl.
   1401                 skipDispatchToOverlayView = KeyEvent.isMediaKey(keyEvent.getKeyCode())
   1402                         || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK;
   1403             } else if (event instanceof MotionEvent) {
   1404                 MotionEvent motionEvent = (MotionEvent) event;
   1405                 final int source = motionEvent.getSource();
   1406                 if (motionEvent.isTouchEvent()) {
   1407                     if (onTouchEvent(motionEvent)) {
   1408                         return TvInputManager.Session.DISPATCH_HANDLED;
   1409                     }
   1410                 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
   1411                     if (onTrackballEvent(motionEvent)) {
   1412                         return TvInputManager.Session.DISPATCH_HANDLED;
   1413                     }
   1414                 } else {
   1415                     if (onGenericMotionEvent(motionEvent)) {
   1416                         return TvInputManager.Session.DISPATCH_HANDLED;
   1417                     }
   1418                 }
   1419             }
   1420             if (mOverlayViewContainer == null || !mOverlayViewContainer.isAttachedToWindow()
   1421                     || skipDispatchToOverlayView) {
   1422                 return TvInputManager.Session.DISPATCH_NOT_HANDLED;
   1423             }
   1424             if (!mOverlayViewContainer.hasWindowFocus()) {
   1425                 mOverlayViewContainer.getViewRootImpl().windowFocusChanged(true, true);
   1426             }
   1427             if (isNavigationKey && mOverlayViewContainer.hasFocusable()) {
   1428                 // If mOverlayView has focusable views, navigation key events should be always
   1429                 // handled. If not, it can make the application UI navigation messed up.
   1430                 // For example, in the case that the left-most view is focused, a left key event
   1431                 // will not be handled in ViewRootImpl. Then, the left key event will be handled in
   1432                 // the application during the UI navigation of the TV input.
   1433                 mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event);
   1434                 return TvInputManager.Session.DISPATCH_HANDLED;
   1435             } else {
   1436                 mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event, receiver);
   1437                 return TvInputManager.Session.DISPATCH_IN_PROGRESS;
   1438             }
   1439         }
   1440 
   1441         private void initialize(ITvInputSessionCallback callback) {
   1442             synchronized(mLock) {
   1443                 mSessionCallback = callback;
   1444                 for (Runnable runnable : mPendingActions) {
   1445                     runnable.run();
   1446                 }
   1447                 mPendingActions.clear();
   1448             }
   1449         }
   1450 
   1451         private void executeOrPostRunnableOnMainThread(Runnable action) {
   1452             synchronized(mLock) {
   1453                 if (mSessionCallback == null) {
   1454                     // The session is not initialized yet.
   1455                     mPendingActions.add(action);
   1456                 } else {
   1457                     if (mHandler.getLooper().isCurrentThread()) {
   1458                         action.run();
   1459                     } else {
   1460                         // Posts the runnable if this is not called from the main thread
   1461                         mHandler.post(action);
   1462                     }
   1463                 }
   1464             }
   1465         }
   1466 
   1467         private final class TimeShiftPositionTrackingRunnable implements Runnable {
   1468             @Override
   1469             public void run() {
   1470                 long startPositionMs = onTimeShiftGetStartPosition();
   1471                 if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME
   1472                         || mStartPositionMs != startPositionMs) {
   1473                     mStartPositionMs = startPositionMs;
   1474                     notifyTimeShiftStartPositionChanged(startPositionMs);
   1475                 }
   1476                 long currentPositionMs = onTimeShiftGetCurrentPosition();
   1477                 if (currentPositionMs < mStartPositionMs) {
   1478                     Log.w(TAG, "Current position (" + currentPositionMs + ") cannot be earlier than"
   1479                             + " start position (" + mStartPositionMs + "). Reset to the start "
   1480                             + "position.");
   1481                     currentPositionMs = mStartPositionMs;
   1482                 }
   1483                 if (mCurrentPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME
   1484                         || mCurrentPositionMs != currentPositionMs) {
   1485                     mCurrentPositionMs = currentPositionMs;
   1486                     notifyTimeShiftCurrentPositionChanged(currentPositionMs);
   1487                 }
   1488                 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
   1489                 mHandler.postDelayed(mTimeShiftPositionTrackingRunnable,
   1490                         POSITION_UPDATE_INTERVAL_MS);
   1491             }
   1492         }
   1493     }
   1494 
   1495     private static final class OverlayViewCleanUpTask extends AsyncTask<View, Void, Void> {
   1496         @Override
   1497         protected Void doInBackground(View... views) {
   1498             View overlayViewParent = views[0];
   1499             try {
   1500                 Thread.sleep(DETACH_OVERLAY_VIEW_TIMEOUT_MS);
   1501             } catch (InterruptedException e) {
   1502                 return null;
   1503             }
   1504             if (isCancelled()) {
   1505                 return null;
   1506             }
   1507             if (overlayViewParent.isAttachedToWindow()) {
   1508                 Log.e(TAG, "Time out on releasing overlay view. Killing "
   1509                         + overlayViewParent.getContext().getPackageName());
   1510                 Process.killProcess(Process.myPid());
   1511             }
   1512             return null;
   1513         }
   1514     }
   1515 
   1516     /**
   1517      * Base class for derived classes to implement to provide a TV input recording session.
   1518      */
   1519     public abstract static class RecordingSession {
   1520         final Handler mHandler;
   1521 
   1522         private final Object mLock = new Object();
   1523         // @GuardedBy("mLock")
   1524         private ITvInputSessionCallback mSessionCallback;
   1525         // @GuardedBy("mLock")
   1526         private final List<Runnable> mPendingActions = new ArrayList<>();
   1527 
   1528         /**
   1529          * Creates a new RecordingSession.
   1530          *
   1531          * @param context The context of the application
   1532          */
   1533         public RecordingSession(Context context) {
   1534             mHandler = new Handler(context.getMainLooper());
   1535         }
   1536 
   1537         /**
   1538          * Informs the application that this recording session has been tuned to the given channel
   1539          * and is ready to start recording.
   1540          *
   1541          * <p>Upon receiving a call to {@link #onTune(Uri)}, the session is expected to tune to the
   1542          * passed channel and call this method to indicate that it is now available for immediate
   1543          * recording. When {@link #onStartRecording(Uri)} is called, recording must start with
   1544          * minimal delay.
   1545          *
   1546          * @param channelUri The URI of a channel.
   1547          */
   1548         public void notifyTuned(Uri channelUri) {
   1549             executeOrPostRunnableOnMainThread(new Runnable() {
   1550                 @MainThread
   1551                 @Override
   1552                 public void run() {
   1553                     try {
   1554                         if (DEBUG) Log.d(TAG, "notifyTuned");
   1555                         if (mSessionCallback != null) {
   1556                             mSessionCallback.onTuned(channelUri);
   1557                         }
   1558                     } catch (RemoteException e) {
   1559                         Log.w(TAG, "error in notifyTuned", e);
   1560                     }
   1561                 }
   1562             });
   1563         }
   1564 
   1565         /**
   1566          * Informs the application that this recording session has stopped recording and created a
   1567          * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly
   1568          * recorded program.
   1569          *
   1570          * <p>The recording session must call this method in response to {@link #onStopRecording()}.
   1571          * The session may call it even before receiving a call to {@link #onStopRecording()} if a
   1572          * partially recorded program is available when there is an error.
   1573          *
   1574          * @param recordedProgramUri The URI of the newly recorded program.
   1575          */
   1576         public void notifyRecordingStopped(final Uri recordedProgramUri) {
   1577             executeOrPostRunnableOnMainThread(new Runnable() {
   1578                 @MainThread
   1579                 @Override
   1580                 public void run() {
   1581                     try {
   1582                         if (DEBUG) Log.d(TAG, "notifyRecordingStopped");
   1583                         if (mSessionCallback != null) {
   1584                             mSessionCallback.onRecordingStopped(recordedProgramUri);
   1585                         }
   1586                     } catch (RemoteException e) {
   1587                         Log.w(TAG, "error in notifyRecordingStopped", e);
   1588                     }
   1589                 }
   1590             });
   1591         }
   1592 
   1593         /**
   1594          * Informs the application that there is an error and this recording session is no longer
   1595          * able to start or continue recording. It may be called at any time after the recording
   1596          * session is created until {@link #onRelease()} is called.
   1597          *
   1598          * <p>The application may release the current session upon receiving the error code through
   1599          * {@link TvRecordingClient.RecordingCallback#onError(int)}. The session may call
   1600          * {@link #notifyRecordingStopped(Uri)} if a partially recorded but still playable program
   1601          * is available, before calling this method.
   1602          *
   1603          * @param error The error code. Should be one of the followings.
   1604          * <ul>
   1605          * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN}
   1606          * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE}
   1607          * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY}
   1608          * </ul>
   1609          */
   1610         public void notifyError(@TvInputManager.RecordingError int error) {
   1611             if (error < TvInputManager.RECORDING_ERROR_START
   1612                     || error > TvInputManager.RECORDING_ERROR_END) {
   1613                 Log.w(TAG, "notifyError - invalid error code (" + error
   1614                         + ") is changed to RECORDING_ERROR_UNKNOWN.");
   1615                 error = TvInputManager.RECORDING_ERROR_UNKNOWN;
   1616             }
   1617             final int validError = error;
   1618             executeOrPostRunnableOnMainThread(new Runnable() {
   1619                 @MainThread
   1620                 @Override
   1621                 public void run() {
   1622                     try {
   1623                         if (DEBUG) Log.d(TAG, "notifyError");
   1624                         if (mSessionCallback != null) {
   1625                             mSessionCallback.onError(validError);
   1626                         }
   1627                     } catch (RemoteException e) {
   1628                         Log.w(TAG, "error in notifyError", e);
   1629                     }
   1630                 }
   1631             });
   1632         }
   1633 
   1634         /**
   1635          * Dispatches an event to the application using this recording session.
   1636          *
   1637          * @param eventType The type of the event.
   1638          * @param eventArgs Optional arguments of the event.
   1639          * @hide
   1640          */
   1641         @SystemApi
   1642         public void notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs) {
   1643             Preconditions.checkNotNull(eventType);
   1644             executeOrPostRunnableOnMainThread(new Runnable() {
   1645                 @MainThread
   1646                 @Override
   1647                 public void run() {
   1648                     try {
   1649                         if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")");
   1650                         if (mSessionCallback != null) {
   1651                             mSessionCallback.onSessionEvent(eventType, eventArgs);
   1652                         }
   1653                     } catch (RemoteException e) {
   1654                         Log.w(TAG, "error in sending event (event=" + eventType + ")", e);
   1655                     }
   1656                 }
   1657             });
   1658         }
   1659 
   1660         /**
   1661          * Called when the application requests to tune to a given channel for TV program recording.
   1662          *
   1663          * <p>The application may call this method before starting or after stopping recording, but
   1664          * not during recording.
   1665          *
   1666          * <p>The session must call {@link #notifyTuned(Uri)} if the tune request was fulfilled, or
   1667          * {@link #notifyError(int)} otherwise.
   1668          *
   1669          * @param channelUri The URI of a channel.
   1670          */
   1671         public abstract void onTune(Uri channelUri);
   1672 
   1673         /**
   1674          * Called when the application requests to tune to a given channel for TV program recording.
   1675          * Override this method in order to handle domain-specific features that are only known
   1676          * between certain TV inputs and their clients.
   1677          *
   1678          * <p>The application may call this method before starting or after stopping recording, but
   1679          * not during recording. The default implementation calls {@link #onTune(Uri)}.
   1680          *
   1681          * <p>The session must call {@link #notifyTuned(Uri)} if the tune request was fulfilled, or
   1682          * {@link #notifyError(int)} otherwise.
   1683          *
   1684          * @param channelUri The URI of a channel.
   1685          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
   1686          *            name, i.e. prefixed with a package name you own, so that different developers
   1687          *            will not create conflicting keys.
   1688          */
   1689         public void onTune(Uri channelUri, Bundle params) {
   1690             onTune(channelUri);
   1691         }
   1692 
   1693         /**
   1694          * Called when the application requests to start TV program recording. Recording must start
   1695          * immediately when this method is called.
   1696          *
   1697          * <p>The application may supply the URI for a TV program for filling in program specific
   1698          * data fields in the {@link android.media.tv.TvContract.RecordedPrograms} table.
   1699          * A non-null {@code programUri} implies the started recording should be of that specific
   1700          * program, whereas null {@code programUri} does not impose such a requirement and the
   1701          * recording can span across multiple TV programs. In either case, the application must call
   1702          * {@link TvRecordingClient#stopRecording()} to stop the recording.
   1703          *
   1704          * <p>The session must call {@link #notifyError(int)} if the start request cannot be
   1705          * fulfilled.
   1706          *
   1707          * @param programUri The URI for the TV program to record, built by
   1708          *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
   1709          */
   1710         public abstract void onStartRecording(@Nullable Uri programUri);
   1711 
   1712         /**
   1713          * Called when the application requests to stop TV program recording. Recording must stop
   1714          * immediately when this method is called.
   1715          *
   1716          * <p>The session must create a new data entry in the
   1717          * {@link android.media.tv.TvContract.RecordedPrograms} table that describes the newly
   1718          * recorded program and call {@link #notifyRecordingStopped(Uri)} with the URI to that
   1719          * entry.
   1720          * If the stop request cannot be fulfilled, the session must call {@link #notifyError(int)}.
   1721          *
   1722          */
   1723         public abstract void onStopRecording();
   1724 
   1725 
   1726         /**
   1727          * Called when the application requests to release all the resources held by this recording
   1728          * session.
   1729          */
   1730         public abstract void onRelease();
   1731 
   1732         /**
   1733          * Processes a private command sent from the application to the TV input. This can be used
   1734          * to provide domain-specific features that are only known between certain TV inputs and
   1735          * their clients.
   1736          *
   1737          * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
   1738          *            i.e. prefixed with a package name you own, so that different developers will
   1739          *            not create conflicting commands.
   1740          * @param data Any data to include with the command.
   1741          */
   1742         public void onAppPrivateCommand(@NonNull String action, Bundle data) {
   1743         }
   1744 
   1745         /**
   1746          * Calls {@link #onTune(Uri, Bundle)}.
   1747          *
   1748          */
   1749         void tune(Uri channelUri, Bundle params) {
   1750             onTune(channelUri, params);
   1751         }
   1752 
   1753         /**
   1754          * Calls {@link #onRelease()}.
   1755          *
   1756          */
   1757         void release() {
   1758             onRelease();
   1759         }
   1760 
   1761         /**
   1762          * Calls {@link #onStartRecording(Uri)}.
   1763          *
   1764          */
   1765         void startRecording(@Nullable  Uri programUri) {
   1766             onStartRecording(programUri);
   1767         }
   1768 
   1769         /**
   1770          * Calls {@link #onStopRecording()}.
   1771          *
   1772          */
   1773         void stopRecording() {
   1774             onStopRecording();
   1775         }
   1776 
   1777         /**
   1778          * Calls {@link #onAppPrivateCommand(String, Bundle)}.
   1779          */
   1780         void appPrivateCommand(String action, Bundle data) {
   1781             onAppPrivateCommand(action, data);
   1782         }
   1783 
   1784         private void initialize(ITvInputSessionCallback callback) {
   1785             synchronized(mLock) {
   1786                 mSessionCallback = callback;
   1787                 for (Runnable runnable : mPendingActions) {
   1788                     runnable.run();
   1789                 }
   1790                 mPendingActions.clear();
   1791             }
   1792         }
   1793 
   1794         private void executeOrPostRunnableOnMainThread(Runnable action) {
   1795             synchronized(mLock) {
   1796                 if (mSessionCallback == null) {
   1797                     // The session is not initialized yet.
   1798                     mPendingActions.add(action);
   1799                 } else {
   1800                     if (mHandler.getLooper().isCurrentThread()) {
   1801                         action.run();
   1802                     } else {
   1803                         // Posts the runnable if this is not called from the main thread
   1804                         mHandler.post(action);
   1805                     }
   1806                 }
   1807             }
   1808         }
   1809     }
   1810 
   1811     /**
   1812      * Base class for a TV input session which represents an external device connected to a
   1813      * hardware TV input.
   1814      *
   1815      * <p>This class is for an input which provides channels for the external set-top box to the
   1816      * application. Once a TV input returns an implementation of this class on
   1817      * {@link #onCreateSession(String)}, the framework will create a separate session for
   1818      * a hardware TV Input (e.g. HDMI 1) and forward the application's surface to the session so
   1819      * that the user can see the screen of the hardware TV Input when she tunes to a channel from
   1820      * this TV input. The implementation of this class is expected to change the channel of the
   1821      * external set-top box via a proprietary protocol when {@link HardwareSession#onTune} is
   1822      * requested by the application.
   1823      *
   1824      * <p>Note that this class is not for inputs for internal hardware like built-in tuner and HDMI
   1825      * 1.
   1826      *
   1827      * @see #onCreateSession(String)
   1828      */
   1829     public abstract static class HardwareSession extends Session {
   1830 
   1831         /**
   1832          * Creates a new HardwareSession.
   1833          *
   1834          * @param context The context of the application
   1835          */
   1836         public HardwareSession(Context context) {
   1837             super(context);
   1838         }
   1839 
   1840         private TvInputManager.Session mHardwareSession;
   1841         private ITvInputSession mProxySession;
   1842         private ITvInputSessionCallback mProxySessionCallback;
   1843         private Handler mServiceHandler;
   1844 
   1845         /**
   1846          * Returns the hardware TV input ID the external device is connected to.
   1847          *
   1848          * <p>TV input is expected to provide {@link android.R.attr#setupActivity} so that
   1849          * the application can launch it before using this TV input. The setup activity may let
   1850          * the user select the hardware TV input to which the external device is connected. The ID
   1851          * of the selected one should be stored in the TV input so that it can be returned here.
   1852          */
   1853         public abstract String getHardwareInputId();
   1854 
   1855         private final TvInputManager.SessionCallback mHardwareSessionCallback =
   1856                 new TvInputManager.SessionCallback() {
   1857             @Override
   1858             public void onSessionCreated(TvInputManager.Session session) {
   1859                 mHardwareSession = session;
   1860                 SomeArgs args = SomeArgs.obtain();
   1861                 if (session != null) {
   1862                     args.arg1 = HardwareSession.this;
   1863                     args.arg2 = mProxySession;
   1864                     args.arg3 = mProxySessionCallback;
   1865                     args.arg4 = session.getToken();
   1866                     session.tune(TvContract.buildChannelUriForPassthroughInput(
   1867                             getHardwareInputId()));
   1868                 } else {
   1869                     args.arg1 = null;
   1870                     args.arg2 = null;
   1871                     args.arg3 = mProxySessionCallback;
   1872                     args.arg4 = null;
   1873                     onRelease();
   1874                 }
   1875                 mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
   1876                         .sendToTarget();
   1877             }
   1878 
   1879             @Override
   1880             public void onVideoAvailable(final TvInputManager.Session session) {
   1881                 if (mHardwareSession == session) {
   1882                     onHardwareVideoAvailable();
   1883                 }
   1884             }
   1885 
   1886             @Override
   1887             public void onVideoUnavailable(final TvInputManager.Session session,
   1888                     final int reason) {
   1889                 if (mHardwareSession == session) {
   1890                     onHardwareVideoUnavailable(reason);
   1891                 }
   1892             }
   1893         };
   1894 
   1895         /**
   1896          * This method will not be called in {@link HardwareSession}. Framework will
   1897          * forward the application's surface to the hardware TV input.
   1898          */
   1899         @Override
   1900         public final boolean onSetSurface(Surface surface) {
   1901             Log.e(TAG, "onSetSurface() should not be called in HardwareProxySession.");
   1902             return false;
   1903         }
   1904 
   1905         /**
   1906          * Called when the underlying hardware TV input session calls
   1907          * {@link TvInputService.Session#notifyVideoAvailable()}.
   1908          */
   1909         public void onHardwareVideoAvailable() { }
   1910 
   1911         /**
   1912          * Called when the underlying hardware TV input session calls
   1913          * {@link TvInputService.Session#notifyVideoUnavailable(int)}.
   1914          *
   1915          * @param reason The reason that the hardware TV input stopped the playback:
   1916          * <ul>
   1917          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
   1918          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
   1919          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
   1920          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
   1921          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
   1922          * </ul>
   1923          */
   1924         public void onHardwareVideoUnavailable(int reason) { }
   1925 
   1926         @Override
   1927         void release() {
   1928             if (mHardwareSession != null) {
   1929                 mHardwareSession.release();
   1930                 mHardwareSession = null;
   1931             }
   1932             super.release();
   1933         }
   1934     }
   1935 
   1936     /** @hide */
   1937     public static boolean isNavigationKey(int keyCode) {
   1938         switch (keyCode) {
   1939             case KeyEvent.KEYCODE_DPAD_LEFT:
   1940             case KeyEvent.KEYCODE_DPAD_RIGHT:
   1941             case KeyEvent.KEYCODE_DPAD_UP:
   1942             case KeyEvent.KEYCODE_DPAD_DOWN:
   1943             case KeyEvent.KEYCODE_DPAD_CENTER:
   1944             case KeyEvent.KEYCODE_PAGE_UP:
   1945             case KeyEvent.KEYCODE_PAGE_DOWN:
   1946             case KeyEvent.KEYCODE_MOVE_HOME:
   1947             case KeyEvent.KEYCODE_MOVE_END:
   1948             case KeyEvent.KEYCODE_TAB:
   1949             case KeyEvent.KEYCODE_SPACE:
   1950             case KeyEvent.KEYCODE_ENTER:
   1951                 return true;
   1952         }
   1953         return false;
   1954     }
   1955 
   1956     @SuppressLint("HandlerLeak")
   1957     private final class ServiceHandler extends Handler {
   1958         private static final int DO_CREATE_SESSION = 1;
   1959         private static final int DO_NOTIFY_SESSION_CREATED = 2;
   1960         private static final int DO_CREATE_RECORDING_SESSION = 3;
   1961         private static final int DO_ADD_HARDWARE_INPUT = 4;
   1962         private static final int DO_REMOVE_HARDWARE_INPUT = 5;
   1963         private static final int DO_ADD_HDMI_INPUT = 6;
   1964         private static final int DO_REMOVE_HDMI_INPUT = 7;
   1965 
   1966         private void broadcastAddHardwareInput(int deviceId, TvInputInfo inputInfo) {
   1967             int n = mCallbacks.beginBroadcast();
   1968             for (int i = 0; i < n; ++i) {
   1969                 try {
   1970                     mCallbacks.getBroadcastItem(i).addHardwareInput(deviceId, inputInfo);
   1971                 } catch (RemoteException e) {
   1972                     Log.e(TAG, "error in broadcastAddHardwareInput", e);
   1973                 }
   1974             }
   1975             mCallbacks.finishBroadcast();
   1976         }
   1977 
   1978         private void broadcastAddHdmiInput(int id, TvInputInfo inputInfo) {
   1979             int n = mCallbacks.beginBroadcast();
   1980             for (int i = 0; i < n; ++i) {
   1981                 try {
   1982                     mCallbacks.getBroadcastItem(i).addHdmiInput(id, inputInfo);
   1983                 } catch (RemoteException e) {
   1984                     Log.e(TAG, "error in broadcastAddHdmiInput", e);
   1985                 }
   1986             }
   1987             mCallbacks.finishBroadcast();
   1988         }
   1989 
   1990         private void broadcastRemoveHardwareInput(String inputId) {
   1991             int n = mCallbacks.beginBroadcast();
   1992             for (int i = 0; i < n; ++i) {
   1993                 try {
   1994                     mCallbacks.getBroadcastItem(i).removeHardwareInput(inputId);
   1995                 } catch (RemoteException e) {
   1996                     Log.e(TAG, "error in broadcastRemoveHardwareInput", e);
   1997                 }
   1998             }
   1999             mCallbacks.finishBroadcast();
   2000         }
   2001 
   2002         @Override
   2003         public final void handleMessage(Message msg) {
   2004             switch (msg.what) {
   2005                 case DO_CREATE_SESSION: {
   2006                     SomeArgs args = (SomeArgs) msg.obj;
   2007                     InputChannel channel = (InputChannel) args.arg1;
   2008                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
   2009                     String inputId = (String) args.arg3;
   2010                     args.recycle();
   2011                     Session sessionImpl = onCreateSession(inputId);
   2012                     if (sessionImpl == null) {
   2013                         try {
   2014                             // Failed to create a session.
   2015                             cb.onSessionCreated(null, null);
   2016                         } catch (RemoteException e) {
   2017                             Log.e(TAG, "error in onSessionCreated", e);
   2018                         }
   2019                         return;
   2020                     }
   2021                     ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
   2022                             sessionImpl, channel);
   2023                     if (sessionImpl instanceof HardwareSession) {
   2024                         HardwareSession proxySession =
   2025                                 ((HardwareSession) sessionImpl);
   2026                         String hardwareInputId = proxySession.getHardwareInputId();
   2027                         if (TextUtils.isEmpty(hardwareInputId) ||
   2028                                 !isPassthroughInput(hardwareInputId)) {
   2029                             if (TextUtils.isEmpty(hardwareInputId)) {
   2030                                 Log.w(TAG, "Hardware input id is not setup yet.");
   2031                             } else {
   2032                                 Log.w(TAG, "Invalid hardware input id : " + hardwareInputId);
   2033                             }
   2034                             sessionImpl.onRelease();
   2035                             try {
   2036                                 cb.onSessionCreated(null, null);
   2037                             } catch (RemoteException e) {
   2038                                 Log.e(TAG, "error in onSessionCreated", e);
   2039                             }
   2040                             return;
   2041                         }
   2042                         proxySession.mProxySession = stub;
   2043                         proxySession.mProxySessionCallback = cb;
   2044                         proxySession.mServiceHandler = mServiceHandler;
   2045                         TvInputManager manager = (TvInputManager) getSystemService(
   2046                                 Context.TV_INPUT_SERVICE);
   2047                         manager.createSession(hardwareInputId,
   2048                                 proxySession.mHardwareSessionCallback, mServiceHandler);
   2049                     } else {
   2050                         SomeArgs someArgs = SomeArgs.obtain();
   2051                         someArgs.arg1 = sessionImpl;
   2052                         someArgs.arg2 = stub;
   2053                         someArgs.arg3 = cb;
   2054                         someArgs.arg4 = null;
   2055                         mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
   2056                                 someArgs).sendToTarget();
   2057                     }
   2058                     return;
   2059                 }
   2060                 case DO_NOTIFY_SESSION_CREATED: {
   2061                     SomeArgs args = (SomeArgs) msg.obj;
   2062                     Session sessionImpl = (Session) args.arg1;
   2063                     ITvInputSession stub = (ITvInputSession) args.arg2;
   2064                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg3;
   2065                     IBinder hardwareSessionToken = (IBinder) args.arg4;
   2066                     try {
   2067                         cb.onSessionCreated(stub, hardwareSessionToken);
   2068                     } catch (RemoteException e) {
   2069                         Log.e(TAG, "error in onSessionCreated", e);
   2070                     }
   2071                     if (sessionImpl != null) {
   2072                         sessionImpl.initialize(cb);
   2073                     }
   2074                     args.recycle();
   2075                     return;
   2076                 }
   2077                 case DO_CREATE_RECORDING_SESSION: {
   2078                     SomeArgs args = (SomeArgs) msg.obj;
   2079                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1;
   2080                     String inputId = (String) args.arg2;
   2081                     args.recycle();
   2082                     RecordingSession recordingSessionImpl = onCreateRecordingSession(inputId);
   2083                     if (recordingSessionImpl == null) {
   2084                         try {
   2085                             // Failed to create a recording session.
   2086                             cb.onSessionCreated(null, null);
   2087                         } catch (RemoteException e) {
   2088                             Log.e(TAG, "error in onSessionCreated", e);
   2089                         }
   2090                         return;
   2091                     }
   2092                     ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
   2093                             recordingSessionImpl);
   2094                     try {
   2095                         cb.onSessionCreated(stub, null);
   2096                     } catch (RemoteException e) {
   2097                         Log.e(TAG, "error in onSessionCreated", e);
   2098                     }
   2099                     recordingSessionImpl.initialize(cb);
   2100                     return;
   2101                 }
   2102                 case DO_ADD_HARDWARE_INPUT: {
   2103                     TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
   2104                     TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
   2105                     if (inputInfo != null) {
   2106                         broadcastAddHardwareInput(hardwareInfo.getDeviceId(), inputInfo);
   2107                     }
   2108                     return;
   2109                 }
   2110                 case DO_REMOVE_HARDWARE_INPUT: {
   2111                     TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
   2112                     String inputId = onHardwareRemoved(hardwareInfo);
   2113                     if (inputId != null) {
   2114                         broadcastRemoveHardwareInput(inputId);
   2115                     }
   2116                     return;
   2117                 }
   2118                 case DO_ADD_HDMI_INPUT: {
   2119                     HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
   2120                     TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
   2121                     if (inputInfo != null) {
   2122                         broadcastAddHdmiInput(deviceInfo.getId(), inputInfo);
   2123                     }
   2124                     return;
   2125                 }
   2126                 case DO_REMOVE_HDMI_INPUT: {
   2127                     HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
   2128                     String inputId = onHdmiDeviceRemoved(deviceInfo);
   2129                     if (inputId != null) {
   2130                         broadcastRemoveHardwareInput(inputId);
   2131                     }
   2132                     return;
   2133                 }
   2134                 default: {
   2135                     Log.w(TAG, "Unhandled message code: " + msg.what);
   2136                     return;
   2137                 }
   2138             }
   2139         }
   2140     }
   2141 }
   2142