blob: 5c4b5288339167a44f865b4b563faef1f56e2876 [file] [log] [blame]
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001/*
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
Jae Seod5cc4a22014-05-30 16:57:43 -070017package android.media.tv;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090018
Jae Seoc8b73562015-04-23 10:53:58 -070019import android.annotation.NonNull;
Dongwon Kang4bf607b2015-04-23 19:44:59 +090020import android.annotation.Nullable;
Jae Seoa759b112014-07-18 22:16:08 -070021import android.annotation.SystemApi;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090022import android.content.Context;
Dongwon Kang5f3cb4a2014-11-19 18:12:44 +090023import android.graphics.Canvas;
24import android.graphics.PorterDuff;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090025import android.graphics.Rect;
Dongwon Kang5f3cb4a2014-11-19 18:12:44 +090026import android.graphics.Region;
Jae Seo3d04b762015-05-12 20:05:00 -070027import android.media.PlaybackParams;
Jae Seod5cc4a22014-05-30 16:57:43 -070028import android.media.tv.TvInputManager.Session;
29import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
30import android.media.tv.TvInputManager.SessionCallback;
Jae Seob8a64412014-06-03 15:34:50 -070031import android.net.Uri;
Youngsang Cho832860f2014-05-21 20:54:03 +090032import android.os.Bundle;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090033import android.os.Handler;
Sungsoo Limd6672b52014-04-30 10:43:26 +090034import android.text.TextUtils;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090035import android.util.AttributeSet;
36import android.util.Log;
Jae Seo411d58d2015-04-22 16:21:08 -070037import android.util.Pair;
Jae Seo6a6059a2014-04-17 21:35:29 -070038import android.view.InputEvent;
39import android.view.KeyEvent;
40import android.view.MotionEvent;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090041import android.view.Surface;
42import android.view.SurfaceHolder;
43import android.view.SurfaceView;
Youngsang Cho07b7c5f2014-06-27 16:21:07 +090044import android.view.View;
Jae Seo1bfce9f2014-06-03 23:58:49 -070045import android.view.ViewGroup;
Chulwoo Leec3e00c32014-05-14 11:31:51 +090046import android.view.ViewRootImpl;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090047
Ji-Hwan Leec7f440d2014-08-21 17:53:15 +090048import java.lang.ref.WeakReference;
Jae Seo411d58d2015-04-22 16:21:08 -070049import java.util.ArrayDeque;
Dongwon Kang1f213912014-07-02 18:35:08 +090050import java.util.List;
Jae Seo411d58d2015-04-22 16:21:08 -070051import java.util.Queue;
Dongwon Kang1f213912014-07-02 18:35:08 +090052
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090053/**
Jae Seo9551e922014-08-05 19:06:47 -070054 * Displays TV contents. The TvView class provides a high level interface for applications to show
55 * TV programs from various TV sources that implement {@link TvInputService}. (Note that the list of
56 * TV inputs available on the system can be obtained by calling
57 * {@link TvInputManager#getTvInputList() TvInputManager.getTvInputList()}.)
Dongwon Kang0610e122015-04-23 17:01:17 +090058 *
59 * <p>Once the application supplies the URI for a specific TV channel to {@link #tune(String, Uri)}
Jae Seo9551e922014-08-05 19:06:47 -070060 * method, it takes care of underlying service binding (and unbinding if the current TvView is
61 * already bound to a service) and automatically allocates/deallocates resources needed. In addition
62 * to a few essential methods to control how the contents are presented, it also provides a way to
63 * dispatch input events to the connected TvInputService in order to enable custom key actions for
64 * the TV input.
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090065 */
Jae Seo1bfce9f2014-06-03 23:58:49 -070066public class TvView extends ViewGroup {
Jae Seob8a64412014-06-03 15:34:50 -070067 private static final String TAG = "TvView";
Jae Seoee2ec052014-09-14 10:30:05 -070068 private static final boolean DEBUG = false;
Jae Seob8a64412014-06-03 15:34:50 -070069
Youngsang Chobf0a4eb2014-08-05 13:32:41 +090070 private static final int ZORDER_MEDIA = 0;
71 private static final int ZORDER_MEDIA_OVERLAY = 1;
72 private static final int ZORDER_ON_TOP = 2;
73
Jae Seo6320fc42014-10-21 16:17:25 -070074 private static final WeakReference<TvView> NULL_TV_VIEW = new WeakReference<>(null);
Ji-Hwan Leec7f440d2014-08-21 17:53:15 +090075
Ji-Hwan Lee4c526972014-07-22 04:46:30 +090076 private static final Object sMainTvViewLock = new Object();
Ji-Hwan Leec7f440d2014-08-21 17:53:15 +090077 private static WeakReference<TvView> sMainTvView = NULL_TV_VIEW;
Ji-Hwan Lee4c526972014-07-22 04:46:30 +090078
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090079 private final Handler mHandler = new Handler();
Jaewan Kim903d6b72014-07-16 11:28:56 +090080 private Session mSession;
Youngsang Cho71b282f2014-07-28 17:10:29 +090081 private SurfaceView mSurfaceView;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +090082 private Surface mSurface;
83 private boolean mOverlayViewCreated;
84 private Rect mOverlayViewFrame;
Jae Seo6a6059a2014-04-17 21:35:29 -070085 private final TvInputManager mTvInputManager;
Jae Seob8a64412014-06-03 15:34:50 -070086 private MySessionCallback mSessionCallback;
Jae Seo2778f5a2014-09-04 14:37:09 -070087 private TvInputCallback mCallback;
Jae Seo6a6059a2014-04-17 21:35:29 -070088 private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
Jae Seo1da8fb32015-04-21 10:50:59 -070089 private Float mStreamVolume;
90 private Boolean mCaptionEnabled;
Jae Seo411d58d2015-04-22 16:21:08 -070091 private final Queue<Pair<String, Bundle>> mPendingAppPrivateCommands = new ArrayDeque<>();
Jae Seo887f5212014-10-23 13:52:42 -070092
Youngsang Choe821d712014-07-16 14:22:19 -070093 private boolean mSurfaceChanged;
94 private int mSurfaceFormat;
95 private int mSurfaceWidth;
96 private int mSurfaceHeight;
Youngsang Cho71b282f2014-07-28 17:10:29 +090097 private final AttributeSet mAttrs;
98 private final int mDefStyleAttr;
Youngsang Chobf0a4eb2014-08-05 13:32:41 +090099 private int mWindowZOrder;
Youngsang Choff04ae72014-07-02 17:08:23 +0900100 private boolean mUseRequestedSurfaceLayout;
101 private int mSurfaceViewLeft;
102 private int mSurfaceViewRight;
103 private int mSurfaceViewTop;
104 private int mSurfaceViewBottom;
Jae Seo465f0d62015-04-06 18:40:46 -0700105 private TimeShiftPositionCallback mTimeShiftPositionCallback;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900106
Jae Seo6a6059a2014-04-17 21:35:29 -0700107 private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900108 @Override
109 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900110 if (DEBUG) {
111 Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width="
112 + width + ", height=" + height + ")");
113 }
Youngsang Choe821d712014-07-16 14:22:19 -0700114 mSurfaceFormat = format;
115 mSurfaceWidth = width;
116 mSurfaceHeight = height;
117 mSurfaceChanged = true;
118 dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900119 }
120
121 @Override
122 public void surfaceCreated(SurfaceHolder holder) {
123 mSurface = holder.getSurface();
124 setSessionSurface(mSurface);
125 }
126
127 @Override
128 public void surfaceDestroyed(SurfaceHolder holder) {
129 mSurface = null;
Youngsang Choe821d712014-07-16 14:22:19 -0700130 mSurfaceChanged = false;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900131 setSessionSurface(null);
132 }
133 };
134
Jae Seo6a6059a2014-04-17 21:35:29 -0700135 private final FinishedInputEventCallback mFinishedInputEventCallback =
136 new FinishedInputEventCallback() {
137 @Override
138 public void onFinishedInputEvent(Object token, boolean handled) {
139 if (DEBUG) {
140 Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")");
141 }
142 if (handled) {
143 return;
144 }
145 // TODO: Re-order unhandled events.
146 InputEvent event = (InputEvent) token;
147 if (dispatchUnhandledInputEvent(event)) {
148 return;
149 }
Chulwoo Leec3e00c32014-05-14 11:31:51 +0900150 ViewRootImpl viewRootImpl = getViewRootImpl();
151 if (viewRootImpl != null) {
152 viewRootImpl.dispatchUnhandledInputEvent(event);
153 }
Jae Seo6a6059a2014-04-17 21:35:29 -0700154 }
155 };
156
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900157 public TvView(Context context) {
158 this(context, null, 0);
159 }
160
161 public TvView(Context context, AttributeSet attrs) {
162 this(context, attrs, 0);
163 }
164
165 public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
166 super(context, attrs, defStyleAttr);
Youngsang Cho71b282f2014-07-28 17:10:29 +0900167 mAttrs = attrs;
168 mDefStyleAttr = defStyleAttr;
169 resetSurfaceView();
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900170 mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
171 }
172
173 /**
Jae Seo2778f5a2014-09-04 14:37:09 -0700174 * Sets the callback to be invoked when an event is dispatched to this TvView.
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900175 *
Jae Seo465f0d62015-04-06 18:40:46 -0700176 * @param callback The callback to receive events. A value of {@code null} removes the existing
177 * callback.
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900178 */
Dongwon Kang4bf607b2015-04-23 19:44:59 +0900179 public void setCallback(@Nullable TvInputCallback callback) {
Jae Seo2778f5a2014-09-04 14:37:09 -0700180 mCallback = callback;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900181 }
182
Jae Seo6a6059a2014-04-17 21:35:29 -0700183 /**
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900184 * Sets this as the main {@link TvView}.
Dongwon Kang0610e122015-04-23 17:01:17 +0900185 *
186 * <p>The main {@link TvView} is a {@link TvView} whose corresponding TV input determines the
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900187 * HDMI-CEC active source device. For an HDMI port input, one of source devices that is
188 * connected to that HDMI port becomes the active source. For an HDMI-CEC logical device input,
189 * the corresponding HDMI-CEC logical device becomes the active source. For any non-HDMI input
190 * (including the tuner, composite, S-Video, etc.), the internal device (= TV itself) becomes
191 * the active source.
Dongwon Kang0610e122015-04-23 17:01:17 +0900192 *
193 * <p>First tuned {@link TvView} becomes main automatically, and keeps to be main until either
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700194 * {@link #reset} is called for the main {@link TvView} or {@code setMain()} is called for other
Ji-Hwan Leec7f440d2014-08-21 17:53:15 +0900195 * {@link TvView}.
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900196 * @hide
197 */
198 @SystemApi
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900199 public void setMain() {
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900200 synchronized (sMainTvViewLock) {
Jae Seo6320fc42014-10-21 16:17:25 -0700201 sMainTvView = new WeakReference<>(this);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900202 if (hasWindowFocus() && mSession != null) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900203 mSession.setMain();
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900204 }
205 }
206 }
207
208 /**
Youngsang Chobf0a4eb2014-08-05 13:32:41 +0900209 * Sets the Z order of a window owning the surface of this TvView above the normal TvView
210 * but below an application.
211 *
212 * @see SurfaceView#setZOrderMediaOverlay
213 * @hide
214 */
215 @SystemApi
216 public void setZOrderMediaOverlay(boolean isMediaOverlay) {
217 if (isMediaOverlay) {
218 mWindowZOrder = ZORDER_MEDIA_OVERLAY;
219 removeSessionOverlayView();
220 } else {
221 mWindowZOrder = ZORDER_MEDIA;
222 createSessionOverlayView();
223 }
224 if (mSurfaceView != null) {
225 // ZOrderOnTop(false) removes WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
226 // from WindowLayoutParam as well as changes window type.
227 mSurfaceView.setZOrderOnTop(false);
228 mSurfaceView.setZOrderMediaOverlay(isMediaOverlay);
229 }
230 }
231
232 /**
233 * Sets the Z order of a window owning the surface of this TvView on top of an application.
234 *
235 * @see SurfaceView#setZOrderOnTop
236 * @hide
237 */
238 @SystemApi
239 public void setZOrderOnTop(boolean onTop) {
240 if (onTop) {
241 mWindowZOrder = ZORDER_ON_TOP;
242 removeSessionOverlayView();
243 } else {
244 mWindowZOrder = ZORDER_MEDIA;
245 createSessionOverlayView();
246 }
247 if (mSurfaceView != null) {
248 mSurfaceView.setZOrderMediaOverlay(false);
249 mSurfaceView.setZOrderOnTop(onTop);
250 }
251 }
252
253 /**
Jae Seo1da8fb32015-04-21 10:50:59 -0700254 * Sets the relative stream volume of this TvView.
Jae Seo782f7342014-06-03 12:51:21 -0700255 *
Jae Seo1da8fb32015-04-21 10:50:59 -0700256 * <p>This method is primarily used to handle audio focus changes or mute a specific TvView when
257 * multiple views are displayed. If the method has not yet been called, the TvView assumes the
258 * default value of {@code 1.0f}.
259 *
260 * @param volume A volume value between {@code 0.0f} to {@code 1.0f}.
Jae Seo782f7342014-06-03 12:51:21 -0700261 */
262 public void setStreamVolume(float volume) {
263 if (DEBUG) Log.d(TAG, "setStreamVolume(" + volume + ")");
Dongwon Kang336cdf22014-06-08 18:20:55 +0900264 mStreamVolume = volume;
Jae Seo782f7342014-06-03 12:51:21 -0700265 if (mSession == null) {
Dongwon Kang336cdf22014-06-08 18:20:55 +0900266 // Volume will be set once the connection has been made.
Jae Seo782f7342014-06-03 12:51:21 -0700267 return;
268 }
269 mSession.setStreamVolume(volume);
270 }
271
272 /**
Jae Seob8a64412014-06-03 15:34:50 -0700273 * Tunes to a given channel.
274 *
Jae Seob1ae00e2015-06-23 02:39:36 -0700275 * @param inputId The ID of the TV input for the given channel.
Jae Seob8a64412014-06-03 15:34:50 -0700276 * @param channelUri The URI of a channel.
277 */
Jae Seoc8b73562015-04-23 10:53:58 -0700278 public void tune(@NonNull String inputId, Uri channelUri) {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +0900279 tune(inputId, channelUri, null);
280 }
281
282 /**
283 * Tunes to a given channel.
284 *
Jae Seob1ae00e2015-06-23 02:39:36 -0700285 * @param inputId The ID of TV input for the given channel.
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +0900286 * @param channelUri The URI of a channel.
Jae Seob1ae00e2015-06-23 02:39:36 -0700287 * @param params Extra parameters.
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +0900288 * @hide
289 */
290 @SystemApi
291 public void tune(String inputId, Uri channelUri, Bundle params) {
Jae Seob8a64412014-06-03 15:34:50 -0700292 if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")");
293 if (TextUtils.isEmpty(inputId)) {
294 throw new IllegalArgumentException("inputId cannot be null or an empty string");
295 }
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900296 synchronized (sMainTvViewLock) {
Ji-Hwan Leec7f440d2014-08-21 17:53:15 +0900297 if (sMainTvView.get() == null) {
Jae Seo6320fc42014-10-21 16:17:25 -0700298 sMainTvView = new WeakReference<>(this);
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900299 }
300 }
Jae Seob1ae00e2015-06-23 02:39:36 -0700301 if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) {
Jae Seob8a64412014-06-03 15:34:50 -0700302 if (mSession != null) {
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +0900303 mSession.tune(channelUri, params);
Jae Seob8a64412014-06-03 15:34:50 -0700304 } else {
Jae Seob1ae00e2015-06-23 02:39:36 -0700305 // createSession() was called but the actual session for the given inputId has not
306 // yet been created. Just replace the existing tuning params in the callback with
307 // the new ones and tune later in onSessionCreated(). It is not necessary to create
308 // a new callback because this tuning request was made on the same inputId.
Jae Seob8a64412014-06-03 15:34:50 -0700309 mSessionCallback.mChannelUri = channelUri;
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +0900310 mSessionCallback.mTuneParams = params;
Jae Seob8a64412014-06-03 15:34:50 -0700311 }
312 } else {
Ji-Hwan Leed0f00582014-08-27 02:55:10 +0900313 resetInternal();
Jae Seob1ae00e2015-06-23 02:39:36 -0700314 // In case createSession() is called multiple times across different inputId's before
315 // any session is created (e.g. when quickly tuning to a channel from input A and then
316 // to another channel from input B), only the callback for the last createSession()
317 // should be invoked. (The previous callbacks are simply ignored.) To do that, we create
318 // a new callback each time and keep mSessionCallback pointing to the last one. If
319 // MySessionCallback.this is different from mSessionCallback, we know that this callback
320 // is obsolete and should ignore it.
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +0900321 mSessionCallback = new MySessionCallback(inputId, channelUri, params);
Jae Seo9127e452014-09-10 13:11:13 -0700322 if (mTvInputManager != null) {
323 mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
324 }
Jae Seob8a64412014-06-03 15:34:50 -0700325 }
326 }
327
328 /**
329 * Resets this TvView.
Dongwon Kang0610e122015-04-23 17:01:17 +0900330 *
331 * <p>This method is primarily used to un-tune the current TvView.
Jae Seob8a64412014-06-03 15:34:50 -0700332 */
333 public void reset() {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900334 if (DEBUG) Log.d(TAG, "reset()");
Ji-Hwan Leec7f440d2014-08-21 17:53:15 +0900335 synchronized (sMainTvViewLock) {
336 if (this == sMainTvView.get()) {
337 sMainTvView = NULL_TV_VIEW;
338 }
339 }
Ji-Hwan Leed0f00582014-08-27 02:55:10 +0900340 resetInternal();
341 }
342
343 private void resetInternal() {
Jae Seob1ae00e2015-06-23 02:39:36 -0700344 mSessionCallback = null;
Jae Seo17345072015-06-20 23:02:29 -0700345 mPendingAppPrivateCommands.clear();
Jae Seob8a64412014-06-03 15:34:50 -0700346 if (mSession != null) {
Jae Seo17345072015-06-20 23:02:29 -0700347 setSessionSurface(null);
348 removeSessionOverlayView();
349 mUseRequestedSurfaceLayout = false;
350 mSession.release();
351 mSession = null;
Youngsang Cho71b282f2014-07-28 17:10:29 +0900352 resetSurfaceView();
Jae Seob8a64412014-06-03 15:34:50 -0700353 }
354 }
355
356 /**
Sungsoo Lim9bf671f2014-07-19 12:59:51 +0900357 * Requests to unblock TV content according to the given rating.
Dongwon Kang0610e122015-04-23 17:01:17 +0900358 *
359 * <p>This notifies TV input that blocked content is now OK to play.
Jaewan Kim903d6b72014-07-16 11:28:56 +0900360 *
Sungsoo Lim9bf671f2014-07-19 12:59:51 +0900361 * @param unblockedRating A TvContentRating to unblock.
Jae Seo91a801d2014-07-24 13:49:03 -0700362 * @see TvInputService.Session#notifyContentBlocked(TvContentRating)
Jaewan Kim903d6b72014-07-16 11:28:56 +0900363 * @hide
Jae Seoa9033832015-03-11 19:29:46 -0700364 * @deprecated Use {@link #unblockContent} instead.
Jaewan Kim903d6b72014-07-16 11:28:56 +0900365 */
Jae Seoa9033832015-03-11 19:29:46 -0700366 @Deprecated
Jae Seo15bbf3b2014-07-20 19:28:08 -0700367 @SystemApi
Sungsoo Lim9bf671f2014-07-19 12:59:51 +0900368 public void requestUnblockContent(TvContentRating unblockedRating) {
Jae Seoa9033832015-03-11 19:29:46 -0700369 unblockContent(unblockedRating);
370 }
371
372 /**
373 * Requests to unblock TV content according to the given rating.
374 *
375 * <p>This notifies TV input that blocked content is now OK to play.
376 *
377 * @param unblockedRating A TvContentRating to unblock.
378 * @see TvInputService.Session#notifyContentBlocked(TvContentRating)
379 * @hide
380 */
381 @SystemApi
382 public void unblockContent(TvContentRating unblockedRating) {
Jaewan Kim903d6b72014-07-16 11:28:56 +0900383 if (mSession != null) {
Jae Seoa9033832015-03-11 19:29:46 -0700384 mSession.unblockContent(unblockedRating);
Jaewan Kim903d6b72014-07-16 11:28:56 +0900385 }
386 }
387
388 /**
Jae Seo2c1c31c2014-07-10 14:57:01 -0700389 * Enables or disables the caption in this TvView.
Dongwon Kang0610e122015-04-23 17:01:17 +0900390 *
391 * <p>Note that this method does not take any effect unless the current TvView is tuned.
Jae Seo2c1c31c2014-07-10 14:57:01 -0700392 *
393 * @param enabled {@code true} to enable, {@code false} to disable.
394 */
395 public void setCaptionEnabled(boolean enabled) {
Jae Seo1da8fb32015-04-21 10:50:59 -0700396 if (DEBUG) Log.d(TAG, "setCaptionEnabled(" + enabled + ")");
397 mCaptionEnabled = enabled;
Jae Seo2c1c31c2014-07-10 14:57:01 -0700398 if (mSession != null) {
399 mSession.setCaptionEnabled(enabled);
400 }
401 }
402
403 /**
Dongwon Kang1f81b102014-08-05 15:13:15 +0900404 * Selects a track.
Jae Seo2c1c31c2014-07-10 14:57:01 -0700405 *
Jae Seo10d285a2014-07-31 22:46:47 +0900406 * @param type The type of the track to select. The type can be {@link TvTrackInfo#TYPE_AUDIO},
407 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
408 * @param trackId The ID of the track to select. {@code null} means to unselect the current
409 * track for a given type.
410 * @see #getTracks
411 * @see #getSelectedTrack
Dongwon Kang1f213912014-07-02 18:35:08 +0900412 */
Jae Seo10d285a2014-07-31 22:46:47 +0900413 public void selectTrack(int type, String trackId) {
Dongwon Kang1f213912014-07-02 18:35:08 +0900414 if (mSession != null) {
Jae Seo10d285a2014-07-31 22:46:47 +0900415 mSession.selectTrack(type, trackId);
Dongwon Kang1f213912014-07-02 18:35:08 +0900416 }
417 }
418
419 /**
Jae Seo10d285a2014-07-31 22:46:47 +0900420 * Returns the list of tracks. Returns {@code null} if the information is not available.
Dongwon Kang1f213912014-07-02 18:35:08 +0900421 *
Jae Seo10d285a2014-07-31 22:46:47 +0900422 * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
423 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
424 * @see #selectTrack
425 * @see #getSelectedTrack
Dongwon Kang1f213912014-07-02 18:35:08 +0900426 */
Jae Seo10d285a2014-07-31 22:46:47 +0900427 public List<TvTrackInfo> getTracks(int type) {
Dongwon Kang1f213912014-07-02 18:35:08 +0900428 if (mSession == null) {
429 return null;
430 }
Jae Seo10d285a2014-07-31 22:46:47 +0900431 return mSession.getTracks(type);
Dongwon Kang1f213912014-07-02 18:35:08 +0900432 }
433
434 /**
Jae Seo10d285a2014-07-31 22:46:47 +0900435 * Returns the ID of the selected track for a given type. Returns {@code null} if the
436 * information is not available or the track is not selected.
437 *
438 * @param type The type of the selected tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
439 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
440 * @see #selectTrack
441 * @see #getTracks
Jae Seod5ce9752014-07-24 15:54:23 -0700442 */
Jae Seo10d285a2014-07-31 22:46:47 +0900443 public String getSelectedTrack(int type) {
Jae Seod5ce9752014-07-24 15:54:23 -0700444 if (mSession == null) {
445 return null;
446 }
Jae Seo10d285a2014-07-31 22:46:47 +0900447 return mSession.getSelectedTrack(type);
Jae Seod5ce9752014-07-24 15:54:23 -0700448 }
449
450 /**
Jae Seoa826d012016-01-18 13:03:35 -0800451 * Plays a given recorded TV program.
452 *
453 * @param inputId The ID of the TV input that created the given recorded program.
454 * @param recordedProgramUri The URI of a recorded program.
455 */
456 public void timeShiftPlay(String inputId, Uri recordedProgramUri) {
457 if (DEBUG) Log.d(TAG, "timeShiftPlay(" + recordedProgramUri + ")");
458 if (TextUtils.isEmpty(inputId)) {
459 throw new IllegalArgumentException("inputId cannot be null or an empty string");
460 }
461 synchronized (sMainTvViewLock) {
462 if (sMainTvView.get() == null) {
463 sMainTvView = new WeakReference<>(this);
464 }
465 }
466 if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) {
467 if (mSession != null) {
468 mSession.timeShiftPlay(recordedProgramUri);
469 } else {
470 mSessionCallback.mRecordedProgramUri = recordedProgramUri;
471 }
472 } else {
473 resetInternal();
474 mSessionCallback = new MySessionCallback(inputId, recordedProgramUri);
475 if (mTvInputManager != null) {
476 mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
477 }
478 }
479 }
480
481 /**
Jae Seo465f0d62015-04-06 18:40:46 -0700482 * Pauses playback. No-op if it is already paused. Call {@link #timeShiftResume} to resume.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700483 */
484 public void timeShiftPause() {
485 if (mSession != null) {
486 mSession.timeShiftPause();
487 }
488 }
489
490 /**
Jae Seo465f0d62015-04-06 18:40:46 -0700491 * Resumes playback. No-op if it is already resumed. Call {@link #timeShiftPause} to pause.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700492 */
493 public void timeShiftResume() {
494 if (mSession != null) {
495 mSession.timeShiftResume();
496 }
497 }
498
499 /**
Jae Seo465f0d62015-04-06 18:40:46 -0700500 * Seeks to a specified time position. {@code timeMs} must be equal to or greater than the start
501 * position returned by {@link TimeShiftPositionCallback#onTimeShiftStartPositionChanged} and
502 * equal to or less than the current time.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700503 *
Jae Seo465f0d62015-04-06 18:40:46 -0700504 * @param timeMs The time position to seek to, in milliseconds since the epoch.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700505 */
506 public void timeShiftSeekTo(long timeMs) {
507 if (mSession != null) {
508 mSession.timeShiftSeekTo(timeMs);
509 }
510 }
511
512 /**
Jae Seo4b34cc72015-05-15 17:29:39 -0700513 * Sets playback rate using {@link android.media.PlaybackParams}.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700514 *
Jae Seo3d04b762015-05-12 20:05:00 -0700515 * @param params The playback params.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700516 */
Jae Seo3d04b762015-05-12 20:05:00 -0700517 public void timeShiftSetPlaybackParams(@NonNull PlaybackParams params) {
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700518 if (mSession != null) {
Jae Seo4b34cc72015-05-15 17:29:39 -0700519 mSession.timeShiftSetPlaybackParams(params);
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700520 }
521 }
522
523 /**
Jae Seo465f0d62015-04-06 18:40:46 -0700524 * Sets the callback to be invoked when the time shift position is changed.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700525 *
Jae Seo465f0d62015-04-06 18:40:46 -0700526 * @param callback The callback to receive time shift position changes. A value of {@code null}
527 * removes the existing callback.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700528 */
Dongwon Kang4bf607b2015-04-23 19:44:59 +0900529 public void setTimeShiftPositionCallback(@Nullable TimeShiftPositionCallback callback) {
Jae Seo465f0d62015-04-06 18:40:46 -0700530 mTimeShiftPositionCallback = callback;
531 ensurePositionTracking();
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700532 }
533
Jae Seo465f0d62015-04-06 18:40:46 -0700534 private void ensurePositionTracking() {
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700535 if (mSession == null) {
536 return;
537 }
Jae Seo465f0d62015-04-06 18:40:46 -0700538 mSession.timeShiftEnablePositionTracking(mTimeShiftPositionCallback != null);
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700539 }
540
541 /**
Jae Seoe3c11e82016-02-08 23:18:49 -0800542 * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)} for the current
543 * session.
Jae Seoa759b112014-07-18 22:16:08 -0700544 *
Jae Seo887f5212014-10-23 13:52:42 -0700545 * @param action The name of the private command to send. This <em>must</em> be a scoped name,
546 * i.e. prefixed with a package name you own, so that different developers will not
547 * create conflicting commands.
548 * @param data An optional bundle to send with the command.
Jae Seoa759b112014-07-18 22:16:08 -0700549 * @hide
550 */
551 @SystemApi
Jae Seoc8b73562015-04-23 10:53:58 -0700552 public void sendAppPrivateCommand(@NonNull String action, Bundle data) {
Jae Seoa759b112014-07-18 22:16:08 -0700553 if (TextUtils.isEmpty(action)) {
554 throw new IllegalArgumentException("action cannot be null or an empty string");
555 }
556 if (mSession != null) {
557 mSession.sendAppPrivateCommand(action, data);
Jae Seo887f5212014-10-23 13:52:42 -0700558 } else {
Jae Seo411d58d2015-04-22 16:21:08 -0700559 Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action
560 + "\" pending)");
561 mPendingAppPrivateCommands.add(Pair.create(action, data));
Jae Seoa759b112014-07-18 22:16:08 -0700562 }
563 }
564
565 /**
Jae Seo6a6059a2014-04-17 21:35:29 -0700566 * Dispatches an unhandled input event to the next receiver.
Dongwon Kang0610e122015-04-23 17:01:17 +0900567 *
568 * <p>Except system keys, TvView always consumes input events in the normal flow. This is called
Jae Seo6a6059a2014-04-17 21:35:29 -0700569 * asynchronously from where the event is dispatched. It gives the host application a chance to
570 * dispatch the unhandled input events.
571 *
572 * @param event The input event.
573 * @return {@code true} if the event was handled by the view, {@code false} otherwise.
574 */
575 public boolean dispatchUnhandledInputEvent(InputEvent event) {
576 if (mOnUnhandledInputEventListener != null) {
577 if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
578 return true;
579 }
580 }
581 return onUnhandledInputEvent(event);
582 }
583
584 /**
Dongwon Kang1f81b102014-08-05 15:13:15 +0900585 * Called when an unhandled input event also has not been handled by the user provided
586 * callback. This is the last chance to handle the unhandled input event in the TvView.
Jae Seo6a6059a2014-04-17 21:35:29 -0700587 *
588 * @param event The input event.
589 * @return If you handled the event, return {@code true}. If you want to allow the event to be
590 * handled by the next receiver, return {@code false}.
591 */
592 public boolean onUnhandledInputEvent(InputEvent event) {
593 return false;
594 }
595
596 /**
Dongwon Kang1f81b102014-08-05 15:13:15 +0900597 * Registers a callback to be invoked when an input event is not handled by the bound TV input.
Jae Seo6a6059a2014-04-17 21:35:29 -0700598 *
Dongwon Kang1f81b102014-08-05 15:13:15 +0900599 * @param listener The callback to be invoked when the unhandled input event is received.
Jae Seo6a6059a2014-04-17 21:35:29 -0700600 */
601 public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
602 mOnUnhandledInputEventListener = listener;
603 }
604
605 @Override
606 public boolean dispatchKeyEvent(KeyEvent event) {
607 if (super.dispatchKeyEvent(event)) {
608 return true;
609 }
610 if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
611 if (mSession == null) {
612 return false;
613 }
Youngsang Chof9826132014-05-13 01:34:49 +0900614 InputEvent copiedEvent = event.copy();
615 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
616 mHandler);
Jae Seo6a6059a2014-04-17 21:35:29 -0700617 return ret != Session.DISPATCH_NOT_HANDLED;
618 }
619
620 @Override
621 public boolean dispatchTouchEvent(MotionEvent event) {
622 if (super.dispatchTouchEvent(event)) {
623 return true;
624 }
625 if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")");
626 if (mSession == null) {
627 return false;
628 }
Youngsang Chof9826132014-05-13 01:34:49 +0900629 InputEvent copiedEvent = event.copy();
630 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
631 mHandler);
Jae Seo6a6059a2014-04-17 21:35:29 -0700632 return ret != Session.DISPATCH_NOT_HANDLED;
633 }
634
635 @Override
636 public boolean dispatchTrackballEvent(MotionEvent event) {
637 if (super.dispatchTrackballEvent(event)) {
638 return true;
639 }
640 if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")");
641 if (mSession == null) {
642 return false;
643 }
Youngsang Chof9826132014-05-13 01:34:49 +0900644 InputEvent copiedEvent = event.copy();
645 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
646 mHandler);
Jae Seo6a6059a2014-04-17 21:35:29 -0700647 return ret != Session.DISPATCH_NOT_HANDLED;
648 }
649
650 @Override
651 public boolean dispatchGenericMotionEvent(MotionEvent event) {
652 if (super.dispatchGenericMotionEvent(event)) {
653 return true;
654 }
655 if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")");
656 if (mSession == null) {
657 return false;
658 }
Youngsang Chof9826132014-05-13 01:34:49 +0900659 InputEvent copiedEvent = event.copy();
660 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
661 mHandler);
Jae Seo6a6059a2014-04-17 21:35:29 -0700662 return ret != Session.DISPATCH_NOT_HANDLED;
663 }
664
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900665 @Override
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900666 public void dispatchWindowFocusChanged(boolean hasFocus) {
667 super.dispatchWindowFocusChanged(hasFocus);
668 // Other app may have shown its own main TvView.
669 // Set main again to regain main session.
670 synchronized (sMainTvViewLock) {
Ji-Hwan Leec7f440d2014-08-21 17:53:15 +0900671 if (hasFocus && this == sMainTvView.get() && mSession != null) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900672 mSession.setMain();
Ji-Hwan Lee4c526972014-07-22 04:46:30 +0900673 }
674 }
675 }
676
677 @Override
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900678 protected void onAttachedToWindow() {
679 super.onAttachedToWindow();
680 createSessionOverlayView();
681 }
682
683 @Override
684 protected void onDetachedFromWindow() {
685 removeSessionOverlayView();
686 super.onDetachedFromWindow();
687 }
688
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900689 @Override
Jae Seo1bfce9f2014-06-03 23:58:49 -0700690 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Youngsang Choff04ae72014-07-02 17:08:23 +0900691 if (DEBUG) {
692 Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
693 + ", bottom=" + bottom + ",)");
694 }
695 if (mUseRequestedSurfaceLayout) {
696 mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
697 mSurfaceViewBottom);
698 } else {
699 mSurfaceView.layout(0, 0, right - left, bottom - top);
700 }
Jae Seo1bfce9f2014-06-03 23:58:49 -0700701 }
702
703 @Override
704 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
705 mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
706 int width = mSurfaceView.getMeasuredWidth();
707 int height = mSurfaceView.getMeasuredHeight();
708 int childState = mSurfaceView.getMeasuredState();
709 setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
710 resolveSizeAndState(height, heightMeasureSpec,
711 childState << MEASURED_HEIGHT_STATE_SHIFT));
712 }
713
714 @Override
Dongwon Kang5f3cb4a2014-11-19 18:12:44 +0900715 public boolean gatherTransparentRegion(Region region) {
716 if (mWindowZOrder != ZORDER_ON_TOP) {
717 if (region != null) {
718 int width = getWidth();
719 int height = getHeight();
720 if (width > 0 && height > 0) {
721 int location[] = new int[2];
722 getLocationInWindow(location);
723 int left = location[0];
724 int top = location[1];
725 region.op(left, top, left + width, top + height, Region.Op.UNION);
726 }
727 }
728 }
729 return super.gatherTransparentRegion(region);
730 }
731
732 @Override
733 public void draw(Canvas canvas) {
734 if (mWindowZOrder != ZORDER_ON_TOP) {
735 // Punch a hole so that the underlying overlay view and surface can be shown.
736 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
737 }
738 super.draw(canvas);
739 }
740
741 @Override
742 protected void dispatchDraw(Canvas canvas) {
743 if (mWindowZOrder != ZORDER_ON_TOP) {
744 // Punch a hole so that the underlying overlay view and surface can be shown.
745 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
746 }
747 super.dispatchDraw(canvas);
748 }
749
750 @Override
Christofer Ã…kersten6a1f2642014-07-28 20:47:19 +0900751 protected void onVisibilityChanged(View changedView, int visibility) {
752 super.onVisibilityChanged(changedView, visibility);
Jae Seo1bfce9f2014-06-03 23:58:49 -0700753 mSurfaceView.setVisibility(visibility);
Youngsang Cho07b7c5f2014-06-27 16:21:07 +0900754 if (visibility == View.VISIBLE) {
755 createSessionOverlayView();
756 } else {
757 removeSessionOverlayView();
758 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900759 }
760
Youngsang Cho71b282f2014-07-28 17:10:29 +0900761 private void resetSurfaceView() {
762 if (mSurfaceView != null) {
763 mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
764 removeView(mSurfaceView);
765 }
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +0900766 mSurface = null;
Youngsang Cho71b282f2014-07-28 17:10:29 +0900767 mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
768 @Override
769 protected void updateWindow(boolean force, boolean redrawNeeded) {
770 super.updateWindow(force, redrawNeeded);
771 relayoutSessionOverlayView();
772 }};
773 mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
Youngsang Chobf0a4eb2014-08-05 13:32:41 +0900774 if (mWindowZOrder == ZORDER_MEDIA_OVERLAY) {
775 mSurfaceView.setZOrderMediaOverlay(true);
776 } else if (mWindowZOrder == ZORDER_ON_TOP) {
777 mSurfaceView.setZOrderOnTop(true);
778 }
Youngsang Cho71b282f2014-07-28 17:10:29 +0900779 addView(mSurfaceView);
780 }
781
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900782 private void setSessionSurface(Surface surface) {
783 if (mSession == null) {
784 return;
785 }
786 mSession.setSurface(surface);
787 }
788
Youngsang Choe821d712014-07-16 14:22:19 -0700789 private void dispatchSurfaceChanged(int format, int width, int height) {
790 if (mSession == null) {
791 return;
792 }
793 mSession.dispatchSurfaceChanged(format, width, height);
794 }
795
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900796 private void createSessionOverlayView() {
797 if (mSession == null || !isAttachedToWindow()
Youngsang Chobf0a4eb2014-08-05 13:32:41 +0900798 || mOverlayViewCreated || mWindowZOrder != ZORDER_MEDIA) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900799 return;
800 }
801 mOverlayViewFrame = getViewFrameOnScreen();
802 mSession.createOverlayView(this, mOverlayViewFrame);
803 mOverlayViewCreated = true;
804 }
805
806 private void removeSessionOverlayView() {
807 if (mSession == null || !mOverlayViewCreated) {
808 return;
809 }
810 mSession.removeOverlayView();
811 mOverlayViewCreated = false;
812 mOverlayViewFrame = null;
813 }
814
815 private void relayoutSessionOverlayView() {
Youngsang Chobf0a4eb2014-08-05 13:32:41 +0900816 if (mSession == null || !isAttachedToWindow() || !mOverlayViewCreated
817 || mWindowZOrder != ZORDER_MEDIA) {
Youngsang Cho9a22f0f2014-04-09 22:51:54 +0900818 return;
819 }
820 Rect viewFrame = getViewFrameOnScreen();
821 if (viewFrame.equals(mOverlayViewFrame)) {
822 return;
823 }
824 mSession.relayoutOverlayView(viewFrame);
825 mOverlayViewFrame = viewFrame;
826 }
827
828 private Rect getViewFrameOnScreen() {
829 int[] location = new int[2];
830 getLocationOnScreen(location);
831 return new Rect(location[0], location[1],
832 location[0] + getWidth(), location[1] + getHeight());
833 }
834
Jae Seo6a6059a2014-04-17 21:35:29 -0700835 /**
Jae Seo465f0d62015-04-06 18:40:46 -0700836 * Callback used to receive time shift position changes.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700837 */
838 public abstract static class TimeShiftPositionCallback {
Jae Seo465f0d62015-04-06 18:40:46 -0700839
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700840 /**
Jae Seo465f0d62015-04-06 18:40:46 -0700841 * This is called when the start playback position is changed.
Dongwon Kang0610e122015-04-23 17:01:17 +0900842 *
843 * <p>The start playback position of the time shifted program can be adjusted by the TV
844 * input when it cannot retain the whole recorded program due to some reason (e.g.
845 * limitation on storage space). The application should not allow the user to seek to a
846 * position earlier than the start position.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700847 *
Jae Seo3d04b762015-05-12 20:05:00 -0700848 * <p>Note that {@code timeMs} is not relative time in the program but wall-clock time,
849 * which is intended to avoid calling this method unnecessarily around program boundaries.
850 *
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700851 * @param inputId The ID of the TV input bound to this view.
Jae Seo465f0d62015-04-06 18:40:46 -0700852 * @param timeMs The start playback position of the time shifted program, in milliseconds
853 * since the epoch.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700854 */
855 public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
856 }
857
858 /**
859 * This is called when the current playback position is changed.
860 *
Jae Seo3d04b762015-05-12 20:05:00 -0700861 * <p>Note that {@code timeMs} is not relative time in the program but wall-clock time,
862 * which is intended to avoid calling this method unnecessarily around program boundaries.
863 *
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700864 * @param inputId The ID of the TV input bound to this view.
Jae Seo465f0d62015-04-06 18:40:46 -0700865 * @param timeMs The current playback position of the time shifted program, in milliseconds
866 * since the epoch.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700867 */
868 public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
869 }
870 }
871
872 /**
Jae Seo2778f5a2014-09-04 14:37:09 -0700873 * Callback used to receive various status updates on the {@link TvView}.
Jae Seob8a64412014-06-03 15:34:50 -0700874 */
Jae Seo2778f5a2014-09-04 14:37:09 -0700875 public abstract static class TvInputCallback {
Jae Seob8a64412014-06-03 15:34:50 -0700876
877 /**
Jae Seo1553a522014-08-18 23:14:12 -0700878 * This is invoked when an error occurred while establishing a connection to the underlying
879 * TV input.
Jae Seob8a64412014-06-03 15:34:50 -0700880 *
881 * @param inputId The ID of the TV input bound to this view.
Jae Seob8a64412014-06-03 15:34:50 -0700882 */
Jae Seo1553a522014-08-18 23:14:12 -0700883 public void onConnectionFailed(String inputId) {
884 }
885
886 /**
887 * This is invoked when the existing connection to the underlying TV input is lost.
888 *
889 * @param inputId The ID of the TV input bound to this view.
890 */
891 public void onDisconnected(String inputId) {
Jae Seob8a64412014-06-03 15:34:50 -0700892 }
893
894 /**
Dongwon Kangb93ccca2014-06-26 17:59:27 +0900895 * This is invoked when the channel of this TvView is changed by the underlying TV input
Jae Seo6320fc42014-10-21 16:17:25 -0700896 * without any {@link TvView#tune(String, Uri)} request.
Dongwon Kangb93ccca2014-06-26 17:59:27 +0900897 *
898 * @param inputId The ID of the TV input bound to this view.
899 * @param channelUri The URI of a channel.
900 */
901 public void onChannelRetuned(String inputId, Uri channelUri) {
902 }
903
904 /**
Dongwon Kang1f213912014-07-02 18:35:08 +0900905 * This is called when the track information has been changed.
906 *
907 * @param inputId The ID of the TV input bound to this view.
908 * @param tracks A list which includes track information.
909 */
Jae Seo10d285a2014-07-31 22:46:47 +0900910 public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
Dongwon Kang1f213912014-07-02 18:35:08 +0900911 }
912
913 /**
Jae Seod5ce9752014-07-24 15:54:23 -0700914 * This is called when there is a change on the selected tracks.
915 *
916 * @param inputId The ID of the TV input bound to this view.
Jae Seo10d285a2014-07-31 22:46:47 +0900917 * @param type The type of the track selected. The type can be
918 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
919 * {@link TvTrackInfo#TYPE_SUBTITLE}.
920 * @param trackId The ID of the track selected.
Jae Seod5ce9752014-07-24 15:54:23 -0700921 */
Jae Seo10d285a2014-07-31 22:46:47 +0900922 public void onTrackSelected(String inputId, int type, String trackId) {
Jae Seod5ce9752014-07-24 15:54:23 -0700923 }
924
925 /**
Jae Seo6320fc42014-10-21 16:17:25 -0700926 * This is invoked when the video size has been changed. It is also called when the first
927 * time video size information becomes available after this view is tuned to a specific
928 * channel.
929 *
930 * @param inputId The ID of the TV input bound to this view.
931 * @param width The width of the video.
932 * @param height The height of the video.
933 */
934 public void onVideoSizeChanged(String inputId, int width, int height) {
935 }
936
937 /**
Dongwon Kang9b08edf2014-07-04 09:32:40 +0900938 * This is called when the video is available, so the TV input starts the playback.
939 *
940 * @param inputId The ID of the TV input bound to this view.
941 */
942 public void onVideoAvailable(String inputId) {
943 }
944
945 /**
946 * This is called when the video is not available, so the TV input stops the playback.
947 *
948 * @param inputId The ID of the TV input bound to this view.
949 * @param reason The reason why the TV input stopped the playback:
950 * <ul>
951 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
Jae Seo6e62a152014-07-29 17:25:47 +0900952 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
Dongwon Kang9b08edf2014-07-04 09:32:40 +0900953 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
954 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
Dongwon Kangff1f29e2015-04-07 17:31:16 -0700955 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
Dongwon Kang9b08edf2014-07-04 09:32:40 +0900956 * </ul>
957 */
958 public void onVideoUnavailable(String inputId, int reason) {
959 }
960
961 /**
Jae Seobbcd2062014-07-18 16:12:38 -0700962 * This is called when the current program content turns out to be allowed to watch since
963 * its content rating is not blocked by parental controls.
964 *
965 * @param inputId The ID of the TV input bound to this view.
966 */
967 public void onContentAllowed(String inputId) {
968 }
969
970 /**
971 * This is called when the current program content turns out to be not allowed to watch
972 * since its content rating is blocked by parental controls.
Jae Seo60571022014-07-14 16:07:19 -0700973 *
974 * @param inputId The ID of the TV input bound to this view.
975 * @param rating The content rating of the blocked program.
976 */
977 public void onContentBlocked(String inputId, TvContentRating rating) {
978 }
979
980 /**
Jae Seob8a64412014-06-03 15:34:50 -0700981 * This is invoked when a custom event from the bound TV input is sent to this view.
982 *
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700983 * @param inputId The ID of the TV input bound to this view.
Jae Seob8a64412014-06-03 15:34:50 -0700984 * @param eventType The type of the event.
985 * @param eventArgs Optional arguments of the event.
986 * @hide
987 */
Jae Seo15bbf3b2014-07-20 19:28:08 -0700988 @SystemApi
Jae Seob8a64412014-06-03 15:34:50 -0700989 public void onEvent(String inputId, String eventType, Bundle eventArgs) {
990 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700991
992 /**
993 * This is called when the time shift status is changed.
994 *
995 * @param inputId The ID of the TV input bound to this view.
Jae Seo465f0d62015-04-06 18:40:46 -0700996 * @param status The current time shift status. Should be one of the followings.
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700997 * <ul>
Jae Seo465f0d62015-04-06 18:40:46 -0700998 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
Dongwon Kang6f0240c2015-03-31 17:56:36 -0700999 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
Jae Seo465f0d62015-04-06 18:40:46 -07001000 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001001 * </ul>
1002 */
1003 public void onTimeShiftStatusChanged(String inputId, int status) {
1004 }
Jae Seob8a64412014-06-03 15:34:50 -07001005 }
1006
1007 /**
Jae Seo6a6059a2014-04-17 21:35:29 -07001008 * Interface definition for a callback to be invoked when the unhandled input event is received.
1009 */
1010 public interface OnUnhandledInputEventListener {
1011 /**
1012 * Called when an input event was not handled by the bound TV input.
Dongwon Kang0610e122015-04-23 17:01:17 +09001013 *
1014 * <p>This is called asynchronously from where the event is dispatched. It gives the host
Jae Seo6a6059a2014-04-17 21:35:29 -07001015 * application a chance to handle the unhandled input events.
1016 *
1017 * @param event The input event.
1018 * @return If you handled the event, return {@code true}. If you want to allow the event to
1019 * be handled by the next receiver, return {@code false}.
1020 */
1021 boolean onUnhandledInputEvent(InputEvent event);
1022 }
1023
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001024 private class MySessionCallback extends SessionCallback {
Jae Seob8a64412014-06-03 15:34:50 -07001025 final String mInputId;
1026 Uri mChannelUri;
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001027 Bundle mTuneParams;
Jae Seoa826d012016-01-18 13:03:35 -08001028 Uri mRecordedProgramUri;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001029
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001030 MySessionCallback(String inputId, Uri channelUri, Bundle tuneParams) {
Jae Seob8a64412014-06-03 15:34:50 -07001031 mInputId = inputId;
1032 mChannelUri = channelUri;
Sungsoo Lim1a6b25e2014-07-09 10:40:43 +09001033 mTuneParams = tuneParams;
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001034 }
1035
Jae Seoa826d012016-01-18 13:03:35 -08001036 MySessionCallback(String inputId, Uri recordedProgramUri) {
1037 mInputId = inputId;
1038 mRecordedProgramUri = recordedProgramUri;
1039 }
1040
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001041 @Override
1042 public void onSessionCreated(Session session) {
Jae Seo6320fc42014-10-21 16:17:25 -07001043 if (DEBUG) {
1044 Log.d(TAG, "onSessionCreated()");
1045 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001046 if (this != mSessionCallback) {
Jae Seo6320fc42014-10-21 16:17:25 -07001047 Log.w(TAG, "onSessionCreated - session already created");
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001048 // This callback is obsolete.
Youngsang Choc31c4502014-05-13 21:13:47 +09001049 if (session != null) {
1050 session.release();
1051 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001052 return;
1053 }
1054 mSession = session;
1055 if (session != null) {
Jae Seo411d58d2015-04-22 16:21:08 -07001056 // Sends the pending app private commands first.
1057 for (Pair<String, Bundle> command : mPendingAppPrivateCommands) {
1058 mSession.sendAppPrivateCommand(command.first, command.second);
1059 }
1060 mPendingAppPrivateCommands.clear();
1061
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001062 synchronized (sMainTvViewLock) {
Ji-Hwan Leec7f440d2014-08-21 17:53:15 +09001063 if (hasWindowFocus() && TvView.this == sMainTvView.get()) {
Ji-Hwan Lee15c56aa2014-08-18 22:01:55 +09001064 mSession.setMain();
Ji-Hwan Lee4c526972014-07-22 04:46:30 +09001065 }
1066 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001067 // mSurface may not be ready yet as soon as starting an application.
1068 // In the case, we don't send Session.setSurface(null) unnecessarily.
1069 // setSessionSurface will be called in surfaceCreated.
1070 if (mSurface != null) {
1071 setSessionSurface(mSurface);
Youngsang Choe821d712014-07-16 14:22:19 -07001072 if (mSurfaceChanged) {
1073 dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
1074 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001075 }
1076 createSessionOverlayView();
Jae Seo1da8fb32015-04-21 10:50:59 -07001077 if (mStreamVolume != null) {
Dongwon Kang336cdf22014-06-08 18:20:55 +09001078 mSession.setStreamVolume(mStreamVolume);
1079 }
Jae Seo1da8fb32015-04-21 10:50:59 -07001080 if (mCaptionEnabled != null) {
1081 mSession.setCaptionEnabled(mCaptionEnabled);
1082 }
Jae Seoa826d012016-01-18 13:03:35 -08001083 if (mChannelUri != null) {
1084 mSession.tune(mChannelUri, mTuneParams);
1085 } else {
1086 mSession.timeShiftPlay(mRecordedProgramUri);
1087 }
Jae Seo465f0d62015-04-06 18:40:46 -07001088 ensurePositionTracking();
Jae Seob8a64412014-06-03 15:34:50 -07001089 } else {
Youngsang Cho7cf67052014-08-26 23:12:59 +09001090 mSessionCallback = null;
Jae Seo2778f5a2014-09-04 14:37:09 -07001091 if (mCallback != null) {
1092 mCallback.onConnectionFailed(mInputId);
Jae Seob8a64412014-06-03 15:34:50 -07001093 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001094 }
1095 }
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001096
1097 @Override
1098 public void onSessionReleased(Session session) {
Jae Seo6320fc42014-10-21 16:17:25 -07001099 if (DEBUG) {
1100 Log.d(TAG, "onSessionReleased()");
1101 }
Youngsang Choe821d712014-07-16 14:22:19 -07001102 if (this != mSessionCallback) {
Jae Seo6320fc42014-10-21 16:17:25 -07001103 Log.w(TAG, "onSessionReleased - session not created");
Youngsang Choe821d712014-07-16 14:22:19 -07001104 return;
Dongwon Kang1f213912014-07-02 18:35:08 +09001105 }
Chulwoo Leeffad70b2014-08-26 19:07:22 +09001106 mOverlayViewCreated = false;
1107 mOverlayViewFrame = null;
Youngsang Choe821d712014-07-16 14:22:19 -07001108 mSessionCallback = null;
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001109 mSession = null;
Jae Seo2778f5a2014-09-04 14:37:09 -07001110 if (mCallback != null) {
1111 mCallback.onDisconnected(mInputId);
Sungsoo Lim2b35a722014-04-17 17:09:15 +09001112 }
1113 }
Youngsang Cho832860f2014-05-21 20:54:03 +09001114
1115 @Override
Dongwon Kangb93ccca2014-06-26 17:59:27 +09001116 public void onChannelRetuned(Session session, Uri channelUri) {
1117 if (DEBUG) {
1118 Log.d(TAG, "onChannelChangedByTvInput(" + channelUri + ")");
1119 }
Jae Seo6320fc42014-10-21 16:17:25 -07001120 if (this != mSessionCallback) {
1121 Log.w(TAG, "onChannelRetuned - session not created");
1122 return;
1123 }
Jae Seo2778f5a2014-09-04 14:37:09 -07001124 if (mCallback != null) {
1125 mCallback.onChannelRetuned(mInputId, channelUri);
Dongwon Kangb93ccca2014-06-26 17:59:27 +09001126 }
1127 }
1128
1129 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001130 public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
Dongwon Kang1f213912014-07-02 18:35:08 +09001131 if (DEBUG) {
Jae Seo6320fc42014-10-21 16:17:25 -07001132 Log.d(TAG, "onTracksChanged(" + tracks + ")");
1133 }
1134 if (this != mSessionCallback) {
1135 Log.w(TAG, "onTracksChanged - session not created");
1136 return;
Dongwon Kang1f213912014-07-02 18:35:08 +09001137 }
Jae Seo2778f5a2014-09-04 14:37:09 -07001138 if (mCallback != null) {
1139 mCallback.onTracksChanged(mInputId, tracks);
Dongwon Kang1f213912014-07-02 18:35:08 +09001140 }
1141 }
1142
Jae Seo60571022014-07-14 16:07:19 -07001143 @Override
Jae Seo10d285a2014-07-31 22:46:47 +09001144 public void onTrackSelected(Session session, int type, String trackId) {
Jae Seo6320fc42014-10-21 16:17:25 -07001145 if (DEBUG) {
1146 Log.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")");
1147 }
Jae Seod5ce9752014-07-24 15:54:23 -07001148 if (this != mSessionCallback) {
Jae Seo6320fc42014-10-21 16:17:25 -07001149 Log.w(TAG, "onTrackSelected - session not created");
Jae Seod5ce9752014-07-24 15:54:23 -07001150 return;
1151 }
Jae Seo2778f5a2014-09-04 14:37:09 -07001152 if (mCallback != null) {
1153 mCallback.onTrackSelected(mInputId, type, trackId);
Jae Seod5ce9752014-07-24 15:54:23 -07001154 }
1155 }
1156
1157 @Override
Jae Seo6320fc42014-10-21 16:17:25 -07001158 public void onVideoSizeChanged(Session session, int width, int height) {
1159 if (DEBUG) {
1160 Log.d(TAG, "onVideoSizeChanged()");
1161 }
Youngsang Choe821d712014-07-16 14:22:19 -07001162 if (this != mSessionCallback) {
Jae Seo6320fc42014-10-21 16:17:25 -07001163 Log.w(TAG, "onVideoSizeChanged - session not created");
Youngsang Choe821d712014-07-16 14:22:19 -07001164 return;
1165 }
Jae Seo6320fc42014-10-21 16:17:25 -07001166 if (mCallback != null) {
1167 mCallback.onVideoSizeChanged(mInputId, width, height);
1168 }
1169 }
1170
1171 @Override
1172 public void onVideoAvailable(Session session) {
Dongwon Kang9b08edf2014-07-04 09:32:40 +09001173 if (DEBUG) {
1174 Log.d(TAG, "onVideoAvailable()");
1175 }
Jae Seo6320fc42014-10-21 16:17:25 -07001176 if (this != mSessionCallback) {
1177 Log.w(TAG, "onVideoAvailable - session not created");
1178 return;
1179 }
Jae Seo2778f5a2014-09-04 14:37:09 -07001180 if (mCallback != null) {
1181 mCallback.onVideoAvailable(mInputId);
Dongwon Kang9b08edf2014-07-04 09:32:40 +09001182 }
1183 }
1184
Jae Seo60571022014-07-14 16:07:19 -07001185 @Override
Dongwon Kang9b08edf2014-07-04 09:32:40 +09001186 public void onVideoUnavailable(Session session, int reason) {
1187 if (DEBUG) {
Jae Seo6320fc42014-10-21 16:17:25 -07001188 Log.d(TAG, "onVideoUnavailable(reason=" + reason + ")");
1189 }
1190 if (this != mSessionCallback) {
1191 Log.w(TAG, "onVideoUnavailable - session not created");
1192 return;
Dongwon Kang9b08edf2014-07-04 09:32:40 +09001193 }
Jae Seo2778f5a2014-09-04 14:37:09 -07001194 if (mCallback != null) {
1195 mCallback.onVideoUnavailable(mInputId, reason);
Dongwon Kang9b08edf2014-07-04 09:32:40 +09001196 }
1197 }
1198
Dongwon Kang1f213912014-07-02 18:35:08 +09001199 @Override
Jae Seobbcd2062014-07-18 16:12:38 -07001200 public void onContentAllowed(Session session) {
Jae Seobbcd2062014-07-18 16:12:38 -07001201 if (DEBUG) {
1202 Log.d(TAG, "onContentAllowed()");
1203 }
Jae Seo6320fc42014-10-21 16:17:25 -07001204 if (this != mSessionCallback) {
1205 Log.w(TAG, "onContentAllowed - session not created");
1206 return;
1207 }
Jae Seo2778f5a2014-09-04 14:37:09 -07001208 if (mCallback != null) {
1209 mCallback.onContentAllowed(mInputId);
Jae Seobbcd2062014-07-18 16:12:38 -07001210 }
1211 }
1212
1213 @Override
Jae Seo60571022014-07-14 16:07:19 -07001214 public void onContentBlocked(Session session, TvContentRating rating) {
1215 if (DEBUG) {
Jae Seo6320fc42014-10-21 16:17:25 -07001216 Log.d(TAG, "onContentBlocked(rating=" + rating + ")");
1217 }
1218 if (this != mSessionCallback) {
1219 Log.w(TAG, "onContentBlocked - session not created");
1220 return;
Jae Seo60571022014-07-14 16:07:19 -07001221 }
Jae Seo2778f5a2014-09-04 14:37:09 -07001222 if (mCallback != null) {
1223 mCallback.onContentBlocked(mInputId, rating);
Jae Seo60571022014-07-14 16:07:19 -07001224 }
1225 }
1226
Jae Seobbcd2062014-07-18 16:12:38 -07001227 @Override
Youngsang Choff04ae72014-07-02 17:08:23 +09001228 public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
1229 if (DEBUG) {
1230 Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
1231 + right + ", bottom=" + bottom + ",)");
1232 }
Jae Seo6320fc42014-10-21 16:17:25 -07001233 if (this != mSessionCallback) {
1234 Log.w(TAG, "onLayoutSurface - session not created");
1235 return;
1236 }
Youngsang Choff04ae72014-07-02 17:08:23 +09001237 mSurfaceViewLeft = left;
1238 mSurfaceViewTop = top;
1239 mSurfaceViewRight = right;
1240 mSurfaceViewBottom = bottom;
1241 mUseRequestedSurfaceLayout = true;
1242 requestLayout();
1243 }
1244
1245 @Override
Jaewan Kim903d6b72014-07-16 11:28:56 +09001246 public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
Jaewan Kim903d6b72014-07-16 11:28:56 +09001247 if (DEBUG) {
1248 Log.d(TAG, "onSessionEvent(" + eventType + ")");
1249 }
Jae Seo6320fc42014-10-21 16:17:25 -07001250 if (this != mSessionCallback) {
1251 Log.w(TAG, "onSessionEvent - session not created");
1252 return;
1253 }
Jae Seo2778f5a2014-09-04 14:37:09 -07001254 if (mCallback != null) {
1255 mCallback.onEvent(mInputId, eventType, eventArgs);
Youngsang Cho832860f2014-05-21 20:54:03 +09001256 }
1257 }
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001258
1259 @Override
1260 public void onTimeShiftStatusChanged(Session session, int status) {
1261 if (DEBUG) {
1262 Log.d(TAG, "onTimeShiftStatusChanged()");
1263 }
1264 if (this != mSessionCallback) {
1265 Log.w(TAG, "onTimeShiftStatusChanged - session not created");
1266 return;
1267 }
1268 if (mCallback != null) {
1269 mCallback.onTimeShiftStatusChanged(mInputId, status);
1270 }
1271 }
1272
1273 @Override
1274 public void onTimeShiftStartPositionChanged(Session session, long timeMs) {
1275 if (DEBUG) {
1276 Log.d(TAG, "onTimeShiftStartPositionChanged()");
1277 }
1278 if (this != mSessionCallback) {
1279 Log.w(TAG, "onTimeShiftStartPositionChanged - session not created");
1280 return;
1281 }
Jae Seo465f0d62015-04-06 18:40:46 -07001282 if (mTimeShiftPositionCallback != null) {
1283 mTimeShiftPositionCallback.onTimeShiftStartPositionChanged(mInputId, timeMs);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001284 }
1285 }
1286
1287 @Override
1288 public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) {
1289 if (DEBUG) {
1290 Log.d(TAG, "onTimeShiftCurrentPositionChanged()");
1291 }
1292 if (this != mSessionCallback) {
1293 Log.w(TAG, "onTimeShiftCurrentPositionChanged - session not created");
1294 return;
1295 }
Jae Seo465f0d62015-04-06 18:40:46 -07001296 if (mTimeShiftPositionCallback != null) {
1297 mTimeShiftPositionCallback.onTimeShiftCurrentPositionChanged(mInputId, timeMs);
Dongwon Kang6f0240c2015-03-31 17:56:36 -07001298 }
1299 }
Youngsang Cho9a22f0f2014-04-09 22:51:54 +09001300 }
1301}