diff options
| -rw-r--r-- | api/current.txt | 24 | ||||
| -rw-r--r-- | api/system-current.txt | 27 | ||||
| -rw-r--r-- | media/java/android/media/tv/ITvInputClient.aidl | 3 | ||||
| -rw-r--r-- | media/java/android/media/tv/ITvInputManager.aidl | 6 | ||||
| -rw-r--r-- | media/java/android/media/tv/ITvInputSession.aidl | 6 | ||||
| -rw-r--r-- | media/java/android/media/tv/ITvInputSessionCallback.aidl | 3 | ||||
| -rw-r--r-- | media/java/android/media/tv/ITvInputSessionWrapper.java | 53 | ||||
| -rw-r--r-- | media/java/android/media/tv/TvInputManager.java | 245 | ||||
| -rw-r--r-- | media/java/android/media/tv/TvInputService.java | 216 | ||||
| -rw-r--r-- | media/java/android/media/tv/TvView.java | 165 | ||||
| -rw-r--r-- | services/core/java/com/android/server/tv/TvInputManagerService.java | 154 |
11 files changed, 880 insertions, 22 deletions
diff --git a/api/current.txt b/api/current.txt index 380fcb4..f11a164 100644 --- a/api/current.txt +++ b/api/current.txt @@ -17466,6 +17466,10 @@ package android.media.tv { field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; + field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L + field public static final int TIME_SHIFT_STATUS_AVAILABLE = 0; // 0x0 + field public static final int TIME_SHIFT_STATUS_ERROR = 2; // 0x2 + field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 1; // 0x1 field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3 field public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; // 0x1 field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0 @@ -17500,6 +17504,8 @@ package android.media.tv { method public void notifyChannelRetuned(android.net.Uri); method public void notifyContentAllowed(); method public void notifyContentBlocked(android.media.tv.TvContentRating); + method public void notifyTimeShiftStartPositionChanged(long); + method public void notifyTimeShiftStatusChanged(int); method public void notifyTrackSelected(int, java.lang.String); method public void notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>); method public void notifyVideoAvailable(); @@ -17516,6 +17522,11 @@ package android.media.tv { method public abstract void onSetStreamVolume(float); method public abstract boolean onSetSurface(android.view.Surface); method public void onSurfaceChanged(int, int, int); + method public long onTimeShiftGetCurrentPosition(); + method public void onTimeShiftPause(); + method public void onTimeShiftResume(); + method public void onTimeShiftSeekTo(long); + method public void onTimeShiftSetPlaybackRate(float, int); method public boolean onTouchEvent(android.view.MotionEvent); method public boolean onTrackballEvent(android.view.MotionEvent); method public abstract boolean onTune(android.net.Uri); @@ -17564,19 +17575,31 @@ package android.media.tv { method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int); method protected void onLayout(boolean, int, int, int, int); method public boolean onUnhandledInputEvent(android.view.InputEvent); + method public void registerTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback); method public void reset(); method public void selectTrack(int, java.lang.String); method public void setCallback(android.media.tv.TvView.TvInputCallback); method public void setCaptionEnabled(boolean); method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener); method public void setStreamVolume(float); + method public void timeShiftPause(); + method public void timeShiftResume(); + method public void timeShiftSeekTo(long); + method public void timeShiftSetPlaybackRate(float, int); method public void tune(java.lang.String, android.net.Uri); + method public void unregisterTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback); } public static abstract interface TvView.OnUnhandledInputEventListener { method public abstract boolean onUnhandledInputEvent(android.view.InputEvent); } + public static abstract class TvView.TimeShiftPositionCallback { + ctor public TvView.TimeShiftPositionCallback(); + method public void onTimeShiftCurrentPositionChanged(java.lang.String, long); + method public void onTimeShiftStartPositionChanged(java.lang.String, long); + } + public static abstract class TvView.TvInputCallback { ctor public TvView.TvInputCallback(); method public void onChannelRetuned(java.lang.String, android.net.Uri); @@ -17584,6 +17607,7 @@ package android.media.tv { method public void onContentAllowed(java.lang.String); method public void onContentBlocked(java.lang.String, android.media.tv.TvContentRating); method public void onDisconnected(java.lang.String); + method public void onTimeShiftStatusChanged(java.lang.String, int); method public void onTrackSelected(java.lang.String, int, java.lang.String); method public void onTracksChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>); method public void onVideoAvailable(java.lang.String); diff --git a/api/system-current.txt b/api/system-current.txt index 40accaa..b1f6fa4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -18828,6 +18828,10 @@ package android.media.tv { field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; + field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L + field public static final int TIME_SHIFT_STATUS_AVAILABLE = 0; // 0x0 + field public static final int TIME_SHIFT_STATUS_ERROR = 2; // 0x2 + field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 1; // 0x1 field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3 field public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; // 0x1 field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0 @@ -18870,6 +18874,9 @@ package android.media.tv { method public void onSessionCreated(android.media.tv.TvInputManager.Session); method public void onSessionEvent(android.media.tv.TvInputManager.Session, java.lang.String, android.os.Bundle); method public void onSessionReleased(android.media.tv.TvInputManager.Session); + method public void onTimeShiftCurrentPositionChanged(android.media.tv.TvInputManager.Session, long); + method public void onTimeShiftStartPositionChanged(android.media.tv.TvInputManager.Session, long); + method public void onTimeShiftStatusChanged(android.media.tv.TvInputManager.Session, int); method public void onTrackSelected(android.media.tv.TvInputManager.Session, int, java.lang.String); method public void onTracksChanged(android.media.tv.TvInputManager.Session, java.util.List<android.media.tv.TvTrackInfo>); method public void onVideoAvailable(android.media.tv.TvInputManager.Session); @@ -18912,6 +18919,8 @@ package android.media.tv { method public void notifyContentAllowed(); method public void notifyContentBlocked(android.media.tv.TvContentRating); method public void notifySessionEvent(java.lang.String, android.os.Bundle); + method public void notifyTimeShiftStartPositionChanged(long); + method public void notifyTimeShiftStatusChanged(int); method public void notifyTrackSelected(int, java.lang.String); method public void notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>); method public void notifyVideoAvailable(); @@ -18931,6 +18940,11 @@ package android.media.tv { method public abstract void onSetStreamVolume(float); method public abstract boolean onSetSurface(android.view.Surface); method public void onSurfaceChanged(int, int, int); + method public long onTimeShiftGetCurrentPosition(); + method public void onTimeShiftPause(); + method public void onTimeShiftResume(); + method public void onTimeShiftSeekTo(long); + method public void onTimeShiftSetPlaybackRate(float, int); method public boolean onTouchEvent(android.view.MotionEvent); method public boolean onTrackballEvent(android.view.MotionEvent); method public abstract boolean onTune(android.net.Uri); @@ -19003,6 +19017,7 @@ package android.media.tv { method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int); method protected void onLayout(boolean, int, int, int, int); method public boolean onUnhandledInputEvent(android.view.InputEvent); + method public void registerTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback); method public void requestUnblockContent(android.media.tv.TvContentRating); method public void reset(); method public void selectTrack(int, java.lang.String); @@ -19014,14 +19029,25 @@ package android.media.tv { method public void setStreamVolume(float); method public void setZOrderMediaOverlay(boolean); method public void setZOrderOnTop(boolean); + method public void timeShiftPause(); + method public void timeShiftResume(); + method public void timeShiftSeekTo(long); + method public void timeShiftSetPlaybackRate(float, int); method public void tune(java.lang.String, android.net.Uri); method public void tune(java.lang.String, android.net.Uri, android.os.Bundle); + method public void unregisterTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback); } public static abstract interface TvView.OnUnhandledInputEventListener { method public abstract boolean onUnhandledInputEvent(android.view.InputEvent); } + public static abstract class TvView.TimeShiftPositionCallback { + ctor public TvView.TimeShiftPositionCallback(); + method public void onTimeShiftCurrentPositionChanged(java.lang.String, long); + method public void onTimeShiftStartPositionChanged(java.lang.String, long); + } + public static abstract class TvView.TvInputCallback { ctor public TvView.TvInputCallback(); method public void onChannelRetuned(java.lang.String, android.net.Uri); @@ -19030,6 +19056,7 @@ package android.media.tv { method public void onContentBlocked(java.lang.String, android.media.tv.TvContentRating); method public void onDisconnected(java.lang.String); method public void onEvent(java.lang.String, java.lang.String, android.os.Bundle); + method public void onTimeShiftStatusChanged(java.lang.String, int); method public void onTrackSelected(java.lang.String, int, java.lang.String); method public void onTracksChanged(java.lang.String, java.util.List<android.media.tv.TvTrackInfo>); method public void onVideoAvailable(java.lang.String); diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index 7a023d6..86c0e5d 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -40,4 +40,7 @@ oneway interface ITvInputClient { void onContentAllowed(int seq); void onContentBlocked(in String rating, int seq); void onLayoutSurface(int left, int top, int right, int bottom, int seq); + void onTimeShiftStatusChanged(int status, int seq); + void onTimeShiftStartPositionChanged(long timeMs, int seq); + void onTimeShiftCurrentPositionChanged(long timeMs, int seq); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 21549c9..f96469e 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -74,6 +74,12 @@ interface ITvInputManager { void requestUnblockContent(in IBinder sessionToken, in String unblockedRating, int userId); + void timeShiftPause(in IBinder sessionToken, int userId); + void timeShiftResume(in IBinder sessionToken, int userId); + void timeShiftSeekTo(in IBinder sessionToken, long timeMs, int userId); + void timeShiftSetPlaybackRate(in IBinder sessionToken, float rate, int audioMode, int userId); + void timeShiftTrackCurrentPosition(in IBinder sessionToken, boolean enabled, int userId); + // For TV input hardware binding List<TvInputHardwareInfo> getHardwareList(); ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback, diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 1aad2fa..306abb8 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -46,4 +46,10 @@ oneway interface ITvInputSession { void removeOverlayView(); void requestUnblockContent(in String unblockedRating); + + void timeShiftPause(); + void timeShiftResume(); + void timeShiftSeekTo(long timeMs); + void timeShiftSetPlaybackRate(float rate, int audioMode); + void timeShiftTrackCurrentPosition(boolean enabled); } diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index 063d10d..e936810 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -37,4 +37,7 @@ oneway interface ITvInputSessionCallback { void onContentAllowed(); void onContentBlocked(in String rating); void onLayoutSurface(int left, int top, int right, int bottom); + void onTimeShiftStatusChanged(int status); + void onTimeShiftStartPositionChanged(long timeMs); + void onTimeShiftCurrentPositionChanged(long timeMs); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 94c9690..f22a8fc 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -57,6 +57,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_RELAYOUT_OVERLAY_VIEW = 11; private static final int DO_REMOVE_OVERLAY_VIEW = 12; private static final int DO_REQUEST_UNBLOCK_CONTENT = 13; + private static final int DO_TIME_SHIFT_PAUSE = 14; + private static final int DO_TIME_SHIFT_RESUME = 15; + private static final int DO_TIME_SHIFT_SEEK_TO = 16; + private static final int DO_TIME_SHIFT_SET_PLAYBACK_RATE = 17; + private static final int DO_TIME_SHIFT_TRACK_CURRENT_POSITION = 18; private final HandlerCaller mCaller; @@ -153,6 +158,26 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.unblockContent((String) msg.obj); break; } + case DO_TIME_SHIFT_PAUSE: { + mTvInputSessionImpl.timeShiftPause(); + break; + } + case DO_TIME_SHIFT_RESUME: { + mTvInputSessionImpl.timeShiftResume(); + break; + } + case DO_TIME_SHIFT_SEEK_TO: { + mTvInputSessionImpl.timeShiftSeekTo((Long) msg.obj); + break; + } + case DO_TIME_SHIFT_SET_PLAYBACK_RATE: { + mTvInputSessionImpl.timeShiftSetPlaybackRate((Float) msg.obj, msg.arg1); + break; + } + case DO_TIME_SHIFT_TRACK_CURRENT_POSITION: { + mTvInputSessionImpl.timeShiftTrackCurrentPosition((Boolean) msg.obj); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -242,6 +267,34 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand DO_REQUEST_UNBLOCK_CONTENT, unblockedRating)); } + @Override + public void timeShiftPause() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_TIME_SHIFT_PAUSE)); + } + + @Override + public void timeShiftResume() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_TIME_SHIFT_RESUME)); + } + + @Override + public void timeShiftSeekTo(long timeMs) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_SEEK_TO, + Long.valueOf(timeMs))); + } + + @Override + public void timeShiftSetPlaybackRate(float rate, int audioMode) { + mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_TIME_SHIFT_SET_PLAYBACK_RATE, + audioMode, Float.valueOf(rate))); + } + + @Override + public void timeShiftTrackCurrentPosition(boolean enabled) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_TRACK_CURRENT_POSITION, + Boolean.valueOf(enabled))); + } + private final class TvInputEventReceiver extends InputEventReceiver { public TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index f55299e..a4d8174 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -71,6 +71,28 @@ public final class TvInputManager { */ public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = VIDEO_UNAVAILABLE_REASON_END; + private static final int TIME_SHIFT_STATUS_START = 0; + private static final int TIME_SHIFT_STATUS_END = 2; + + /** + * Time shifting is available. In this status, the application can pause/resume the playback, + * seek to a specific position, and change the playback rate. + */ + public static final int TIME_SHIFT_STATUS_AVAILABLE = TIME_SHIFT_STATUS_START; + + /** + * Time shifting is not available. + */ + public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 1; + + /** + * An error occurred while handling a time shift request. To recover the status, tune to a + * new channel. + */ + public static final int TIME_SHIFT_STATUS_ERROR = TIME_SHIFT_STATUS_END; + + public static final long TIME_SHIFT_INVALID_TIME = Long.MIN_VALUE; + /** * The TV input is in unknown state. * <p> @@ -271,7 +293,7 @@ public final class TvInputManager { /** * This is called when the video is not available, so the TV input stops the playback. * - * @param session A {@link TvInputManager.Session} associated with this callback + * @param session A {@link TvInputManager.Session} associated with this callback. * @param reason The reason why the TV input stopped the playback: * <ul> * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN} @@ -287,7 +309,7 @@ public final class TvInputManager { * This is called when the current program content turns out to be allowed to watch since * its content rating is not blocked by parental controls. * - * @param session A {@link TvInputManager.Session} associated with this callback + * @param session A {@link TvInputManager.Session} associated with this callback. */ public void onContentAllowed(Session session) { } @@ -296,7 +318,7 @@ public final class TvInputManager { * This is called when the current program content turns out to be not allowed to watch * since its content rating is blocked by parental controls. * - * @param session A {@link TvInputManager.Session} associated with this callback + * @param session A {@link TvInputManager.Session} associated with this callback. * @param rating The content ration of the blocked program. */ public void onContentBlocked(Session session, TvContentRating rating) { @@ -306,7 +328,7 @@ public final class TvInputManager { * This is called when {@link TvInputService.Session#layoutSurface} is called to change the * layout of surface. * - * @param session A {@link TvInputManager.Session} associated with this callback + * @param session A {@link TvInputManager.Session} associated with this callback. * @param left Left position. * @param top Top position. * @param right Right position. @@ -328,6 +350,40 @@ public final class TvInputManager { @SystemApi public void onSessionEvent(Session session, String eventType, Bundle eventArgs) { } + + /** + * This is called when the trick play status is changed. + * + * @param session A {@link TvInputManager.Session} associated with this callback. + * @param status The current time shift status: + * <ul> + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_ERROR} + * </ul> + */ + public void onTimeShiftStatusChanged(Session session, int status) { + } + + /** + * This is called when the time shift start position is changed. The application may seek to + * a position in the range from the start position and the current time, inclusive. + * + * @param session A {@link TvInputManager.Session} associated with this callback. + * @param timeMs The start of the possible time shift range, in milliseconds since the + * epoch. + */ + public void onTimeShiftStartPositionChanged(Session session, long timeMs) { + } + + /** + * This is called when the current position is changed. + * + * @param session A {@link TvInputManager.Session} associated with this callback. + * @param timeMs The current position, in milliseconds since the epoch. + */ + public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) { + } } private static final class SessionCallbackRecord { @@ -450,6 +506,33 @@ public final class TvInputManager { } }); } + + void postTimeShiftStatusChanged(final int status) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onTimeShiftStatusChanged(mSession, status); + } + }); + } + + void postTimeShiftStartPositionChanged(final long timeMs) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onTimeShiftStartPositionChanged(mSession, timeMs); + } + }); + } + + void postTimeShiftCurrentPositionChanged(final long timeMs) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onTimeShiftCurrentPositionChanged(mSession, timeMs); + } + }); + } } /** @@ -718,6 +801,42 @@ public final class TvInputManager { record.postSessionEvent(eventType, eventArgs); } } + + @Override + public void onTimeShiftStatusChanged(int status, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postTimeShiftStatusChanged(status); + } + } + + @Override + public void onTimeShiftStartPositionChanged(long timeMs, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postTimeShiftStartPositionChanged(timeMs); + } + } + + @Override + public void onTimeShiftCurrentPositionChanged(long timeMs, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postTimeShiftCurrentPositionChanged(timeMs); + } + } }; mManagerCallback = new ITvInputManagerCallback.Stub() { @Override @@ -1171,22 +1290,22 @@ public final class TvInputManager { private TvInputEventSender mSender; private InputChannel mChannel; - private final Object mTrackLock = new Object(); - // @GuardedBy("mTrackLock") + private final Object mMetadataLock = new Object(); + // @GuardedBy("mMetadataLock") private final List<TvTrackInfo> mAudioTracks = new ArrayList<TvTrackInfo>(); - // @GuardedBy("mTrackLock") + // @GuardedBy("mMetadataLock") private final List<TvTrackInfo> mVideoTracks = new ArrayList<TvTrackInfo>(); - // @GuardedBy("mTrackLock") + // @GuardedBy("mMetadataLock") private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<TvTrackInfo>(); - // @GuardedBy("mTrackLock") + // @GuardedBy("mMetadataLock") private String mSelectedAudioTrackId; - // @GuardedBy("mTrackLock") + // @GuardedBy("mMetadataLock") private String mSelectedVideoTrackId; - // @GuardedBy("mTrackLock") + // @GuardedBy("mMetadataLock") private String mSelectedSubtitleTrackId; - // @GuardedBy("mTrackLock") + // @GuardedBy("mMetadataLock") private int mVideoWidth; - // @GuardedBy("mTrackLock") + // @GuardedBy("mMetadataLock") private int mVideoHeight; private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId, @@ -1322,7 +1441,7 @@ public final class TvInputManager { Log.w(TAG, "The session has been already released"); return; } - synchronized (mTrackLock) { + synchronized (mMetadataLock) { mAudioTracks.clear(); mVideoTracks.clear(); mSubtitleTracks.clear(); @@ -1367,7 +1486,7 @@ public final class TvInputManager { * @see #getTracks */ public void selectTrack(int type, String trackId) { - synchronized (mTrackLock) { + synchronized (mMetadataLock) { if (type == TvTrackInfo.TYPE_AUDIO) { if (trackId != null && !containsTrack(mAudioTracks, trackId)) { Log.w(TAG, "Invalid audio trackId: " + trackId); @@ -1416,7 +1535,7 @@ public final class TvInputManager { * @return the list of tracks for the given type. */ public List<TvTrackInfo> getTracks(int type) { - synchronized (mTrackLock) { + synchronized (mMetadataLock) { if (type == TvTrackInfo.TYPE_AUDIO) { if (mAudioTracks == null) { return null; @@ -1445,7 +1564,7 @@ public final class TvInputManager { * @see #selectTrack */ public String getSelectedTrack(int type) { - synchronized (mTrackLock) { + synchronized (mMetadataLock) { if (type == TvTrackInfo.TYPE_AUDIO) { return mSelectedAudioTrackId; } else if (type == TvTrackInfo.TYPE_VIDEO) { @@ -1462,7 +1581,7 @@ public final class TvInputManager { * there is an update. */ boolean updateTracks(List<TvTrackInfo> tracks) { - synchronized (mTrackLock) { + synchronized (mMetadataLock) { mAudioTracks.clear(); mVideoTracks.clear(); mSubtitleTracks.clear(); @@ -1485,7 +1604,7 @@ public final class TvInputManager { * Returns true if there is an update. */ boolean updateTrackSelection(int type, String trackId) { - synchronized (mTrackLock) { + synchronized (mMetadataLock) { if (type == TvTrackInfo.TYPE_AUDIO && trackId != mSelectedAudioTrackId) { mSelectedAudioTrackId = trackId; return true; @@ -1509,7 +1628,7 @@ public final class TvInputManager { * track. */ TvTrackInfo getVideoTrackToNotify() { - synchronized (mTrackLock) { + synchronized (mMetadataLock) { if (!mVideoTracks.isEmpty() && mSelectedVideoTrackId != null) { for (TvTrackInfo track : mVideoTracks) { if (track.getId().equals(mSelectedVideoTrackId)) { @@ -1528,6 +1647,92 @@ public final class TvInputManager { } /** + * Pauses the playback. Call {@link #timeShiftResume()} to restart the playback. + */ + void timeShiftPause() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.timeShiftPause(mToken, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Resumes the playback. No-op if it is already playing the channel. + */ + void timeShiftResume() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.timeShiftResume(mToken, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Seeks to the specific time position. The position should be in the range from the start + * time from the start time, + * {@link TvInputCallback#onTimeShiftStartPositionChanged(String, long)}, to the current + * time, inclusive. + * + * @param timeMs The target time, in milliseconds since the epoch. + */ + void timeShiftSeekTo(long timeMs) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.timeShiftSeekTo(mToken, timeMs, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Sets a playback rate and an audio mode. + * + * @param rate The ratio between desired playback rate and normal one. + * @param audioMode The audio playback mode. Must be one of the supported audio modes: + * <ul> + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} + * </ul> + */ + void timeShiftSetPlaybackRate(float rate, int audioMode) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.timeShiftSetPlaybackRate(mToken, rate, audioMode, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns the current playback position. + */ + void timeShiftTrackCurrentPosition(boolean enabled) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.timeShiftTrackCurrentPosition(mToken, enabled, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle) * TvInputService.Session.appPrivateCommand()} on the current TvView. * diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 8ed383a..93abc2b 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -235,7 +235,9 @@ public abstract class TvInputService extends Service { * Base class for derived classes to implement to provide a TV input session. */ public abstract static class Session implements KeyEvent.Callback { - private static final int DETACH_OVERLAY_VIEW_TIMEOUT = 5000; + private static final int DETACH_OVERLAY_VIEW_TIMEOUT_MS = 5000; + private static final int POSITION_UPDATE_INTERVAL_MS = 1000; + private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); private final WindowManager mWindowManager; final Handler mHandler; @@ -248,6 +250,10 @@ public abstract class TvInputService extends Service { private boolean mOverlayViewEnabled; private IBinder mWindowToken; private Rect mOverlayFrame; + private long mCurrentPositionMs; + private final TimeShiftCurrentPositionTrackingRunnable + mTimeShiftCurrentPositionTrackingRunnable = + new TimeShiftCurrentPositionTrackingRunnable(); private final Object mLock = new Object(); // @GuardedBy("mLock") @@ -264,6 +270,7 @@ public abstract class TvInputService extends Service { mContext = context; mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mHandler = new Handler(context.getMainLooper()); + mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; } /** @@ -550,6 +557,89 @@ public abstract class TvInputService extends Service { } /** + * Informs the application that the trick play status is changed. + * <p> + * The application assumes that time shift is not available by default. So, the + * implementation should call this method with + * {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} on tune request, if the time shift is + * available in the given channel. + * Note that sending {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} means the session + * implemented {@link #onTimeShiftPause}, {@link #onTimeShiftResume}, + * {@link #onTimeShiftSeekTo}, {@link #onTimeShiftGetCurrentPosition}, and + * {@link #onTimeShiftSetPlaybackRate}, and these are working at the moment. + * </p> + * + * @param status The current time shift status: + * <ul> + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_ERROR} + * </ul> + */ + public void notifyTimeShiftStatusChanged(final int status) { + executeOrPostRunnable(new Runnable() { + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyTimeShiftStatusChanged"); + if (mSessionCallback != null) { + mSessionCallback.onTimeShiftStatusChanged(status); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyTimeShiftStatusChanged"); + } + } + }); + } + + /** + * Informs the application that the time shift start position is changed. + * <p> + * The application may seek to a position in the range from the start position and the + * current time, inclusive. So, the implementation should call this whenever the range is + * updated. + * </p> + * + * @param timeMs the start of possible time shift range, in milliseconds since the epoch. + */ + public void notifyTimeShiftStartPositionChanged(final long timeMs) { + executeOrPostRunnable(new Runnable() { + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyTimeShiftStartPositionChanged"); + if (mSessionCallback != null) { + mSessionCallback.onTimeShiftStartPositionChanged(timeMs); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyTimeShiftStartPositionChanged"); + } + } + }); + } + + /** + * Informs the application that the current playback position is changed. + * + * @param timeMs The current position, in milliseconds since the epoch. + */ + private void notifyTimeShiftCurrentPositionChanged(final long timeMs) { + executeOrPostRunnable(new Runnable() { + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyTimeShiftCurrentPositionChanged"); + if (mSessionCallback != null) { + mSessionCallback.onTimeShiftCurrentPositionChanged(timeMs); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyTimeShiftCurrentPositionChanged"); + } + } + }); + } + + /** * Assigns a position of the {@link Surface} passed by {@link #onSetSurface}. The position * is relative to an overlay view. * @@ -756,6 +846,72 @@ public abstract class TvInputService extends Service { } /** + * Called when an application requests to pause the playback. + * + * @see #onTimeShiftResume() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftSetPlaybackRate(float, int) + * @see #onTimeShiftGetCurrentPosition() + */ + public void onTimeShiftPause() { + } + + /** + * Called when an application requests to resume the playback. + * + * @see #onTimeShiftPause() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftSetPlaybackRate(float, int) + * @see #onTimeShiftGetCurrentPosition() + */ + public void onTimeShiftResume() { + } + + /** + * Called when an application requests to seek to a specific position. The {@code timeMs} is + * expected to be in a range from the start time, + * {@link #notifyTimeShiftStartPositionChanged(long)}, to the current time, inclusive. If it + * is not, the implementation should seek to the nearest time position in the range. + * + * @param timeMs The target time, in milliseconds since the epoch + * @see #onTimeShiftResume() + * @see #onTimeShiftPause() + * @see #onTimeShiftSetPlaybackRate(float, int) + * @see #onTimeShiftGetCurrentPosition() + */ + public void onTimeShiftSeekTo(long timeMs) { + } + + /** + * Called when an application sets a playback rate and an audio mode. + * + * @param rate The ratio between desired playback rate and normal one. + * @param audioMode The audio playback mode. Must be one of the supported audio modes: + * <ul> + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} + * </ul> + * @see #onTimeShiftResume() + * @see #onTimeShiftPause() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftGetCurrentPosition() + */ + public void onTimeShiftSetPlaybackRate(float rate, int audioMode) { + } + + /** + * Returns the current playback position in milliseconds since the epoch. + * {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if position is unknown at this moment. + * + * @see #onTimeShiftResume() + * @see #onTimeShiftPause() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftSetPlaybackRate(float, int) + */ + public long onTimeShiftGetCurrentPosition() { + return TvInputManager.TIME_SHIFT_INVALID_TIME; + } + + /** * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent) * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event). * <p> @@ -887,6 +1043,7 @@ public abstract class TvInputService extends Service { // Removes the overlay view lastly so that any hanging on the main thread can be handled // in {@link #scheduleOverlayViewCleanup}. removeOverlayView(true); + mHandler.removeCallbacks(mTimeShiftCurrentPositionTrackingRunnable); } /** @@ -930,6 +1087,7 @@ public abstract class TvInputService extends Service { * Calls {@link #onTune}. */ void tune(Uri channelUri, Bundle params) { + mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; onTune(channelUri, params); // TODO: Handle failure. } @@ -1059,6 +1217,46 @@ public abstract class TvInputService extends Service { } /** + * Calls {@link #onTimeShiftPause}. + */ + void timeShiftPause() { + onTimeShiftPause(); + } + + /** + * Calls {@link #onTimeShiftResume}. + */ + void timeShiftResume() { + onTimeShiftResume(); + } + + /** + * Calls {@link #onTimeShiftSeekTo}. + */ + void timeShiftSeekTo(long timeMs) { + onTimeShiftSeekTo(timeMs); + } + + /** + * Calls {@link #onTimeShiftSetPlaybackRate}. + */ + void timeShiftSetPlaybackRate(float rate, int audioMode) { + onTimeShiftSetPlaybackRate(rate, audioMode); + } + + /** + * Turns on/off the current position tracking. + */ + void timeShiftTrackCurrentPosition(boolean enabled) { + if (enabled) { + mHandler.post(mTimeShiftCurrentPositionTrackingRunnable); + } else { + mHandler.removeCallbacks(mTimeShiftCurrentPositionTrackingRunnable); + mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; + } + } + + /** * Schedules a task which checks whether the overlay view is detached and kills the process * if it is not. Note that this method is expected to be called in a non-main thread. */ @@ -1154,12 +1352,26 @@ public abstract class TvInputService extends Service { } } + private final class TimeShiftCurrentPositionTrackingRunnable implements Runnable { + @Override + public void run() { + long pos = onTimeShiftGetCurrentPosition(); + if (mCurrentPositionMs != pos) { + mCurrentPositionMs = pos; + notifyTimeShiftCurrentPositionChanged(pos); + } + mHandler.removeCallbacks(mTimeShiftCurrentPositionTrackingRunnable); + mHandler.postDelayed(mTimeShiftCurrentPositionTrackingRunnable, + POSITION_UPDATE_INTERVAL_MS); + } + } + private final class OverlayViewCleanUpTask extends AsyncTask<View, Void, Void> { @Override protected Void doInBackground(View... views) { View overlayViewParent = views[0]; try { - Thread.sleep(DETACH_OVERLAY_VIEW_TIMEOUT); + Thread.sleep(DETACH_OVERLAY_VIEW_TIMEOUT_MS); } catch (InterruptedException e) { return null; } diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 6fc1b82..115d094 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -42,6 +42,7 @@ import android.view.ViewGroup; import android.view.ViewRootImpl; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.List; /** @@ -103,6 +104,7 @@ public class TvView extends ViewGroup { private int mSurfaceViewRight; private int mSurfaceViewTop; private int mSurfaceViewBottom; + private List<TimeShiftPositionCallback> mTimeShiftPositionCallbacks = new ArrayList<>(); private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { @Override @@ -420,6 +422,85 @@ public class TvView extends ViewGroup { } /** + * Pauses the playback. Call {@link #timeShiftResume()} to restart the playback. + */ + public void timeShiftPause() { + if (mSession != null) { + mSession.timeShiftPause(); + } + } + + /** + * Resumes the playback. No-op if it is already playing the channel. + */ + public void timeShiftResume() { + if (mSession != null) { + mSession.timeShiftResume(); + } + } + + /** + * Seeks to the specific time position. The position should be in the range from the start time + * from the start time, {@link TimeShiftPositionCallback#onTimeShiftStartPositionChanged}, + * to the current time, inclusive. + * + * @param timeMs The target time, in milliseconds since the epoch. + */ + public void timeShiftSeekTo(long timeMs) { + if (mSession != null) { + mSession.timeShiftSeekTo(timeMs); + } + } + + /** + * Sets a playback rate and an audio mode. + * + * @param rate The ratio between desired playback rate and normal one. + * @param audioMode The audio playback mode. Must be one of the supported audio modes: + * <ul> + * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} + * </ul> + */ + public void timeShiftSetPlaybackRate(float rate, int audioMode) { + if (mSession != null) { + mSession.timeShiftSetPlaybackRate(rate, audioMode); + } + } + + /** + * Registers a {@link TvView.TimeShiftPositionCallback}. + * + * @param callback A callback used to monitor the time shift range and current position. + */ + public void registerTimeShiftPositionCallback(TimeShiftPositionCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback can not be null."); + } + mTimeShiftPositionCallbacks.add(callback); + ensureCurrentPositionTracking(); + } + + /** + * Unregisters the existing {@link TvView.TimeShiftPositionCallback}. + * + * @param callback The existing callback to remove. + */ + public void unregisterTimeShiftPositionCallback(TimeShiftPositionCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback can not be null."); + } + mTimeShiftPositionCallbacks.remove(callback); + ensureCurrentPositionTracking(); + } + + private void ensureCurrentPositionTracking() { + if (mSession == null) { + return; + } + mSession.timeShiftTrackCurrentPosition(!mTimeShiftPositionCallbacks.isEmpty()); + } + + /** * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle) * TvInputService.Session.appPrivateCommand()} on the current TvView. * @@ -729,6 +810,32 @@ public class TvView extends ViewGroup { } /** + * Callback used to receive the information on the possible range for time shifting and currrent + * position. + */ + public abstract static class TimeShiftPositionCallback { + /** + * This is called when the time shift start position is changed. The application may seek to + * a position in the range from the start position and the current time, inclusive. + * + * @param inputId The ID of the TV input bound to this view. + * @param timeMs the start of the possible time shift range, in milliseconds since the + * epoch. + */ + public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { + } + + /** + * This is called when the current playback position is changed. + * + * @param inputId The ID of the TV input bound to this view. + * @param timeMs The current position, in milliseconds since the epoch. + */ + public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { + } + } + + /** * Callback used to receive various status updates on the {@link TvView}. */ public abstract static class TvInputCallback { @@ -838,6 +945,7 @@ public class TvView extends ViewGroup { /** * This is invoked when a custom event from the bound TV input is sent to this view. * + * @param inputId The ID of the TV input bound to this view. * @param eventType The type of the event. * @param eventArgs Optional arguments of the event. * @hide @@ -845,6 +953,20 @@ public class TvView extends ViewGroup { @SystemApi public void onEvent(String inputId, String eventType, Bundle eventArgs) { } + + /** + * This is called when the time shift status is changed. + * + * @param inputId The ID of the TV input bound to this view. + * @param status The current time shift status: + * <ul> + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_ERROR} + * </ul> + */ + public void onTimeShiftStatusChanged(String inputId, int status) { + } } /** @@ -918,6 +1040,7 @@ public class TvView extends ViewGroup { mAppPrivateCommandAction = null; mAppPrivateCommandData = null; } + ensureCurrentPositionTracking(); } else { mSessionCallback = null; if (mCallback != null) { @@ -1087,5 +1210,47 @@ public class TvView extends ViewGroup { mCallback.onEvent(mInputId, eventType, eventArgs); } } + + @Override + public void onTimeShiftStatusChanged(Session session, int status) { + if (DEBUG) { + Log.d(TAG, "onTimeShiftStatusChanged()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onTimeShiftStatusChanged - session not created"); + return; + } + if (mCallback != null) { + mCallback.onTimeShiftStatusChanged(mInputId, status); + } + } + + @Override + public void onTimeShiftStartPositionChanged(Session session, long timeMs) { + if (DEBUG) { + Log.d(TAG, "onTimeShiftStartPositionChanged()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onTimeShiftStartPositionChanged - session not created"); + return; + } + for (TimeShiftPositionCallback callback : mTimeShiftPositionCallbacks) { + callback.onTimeShiftStartPositionChanged(mInputId, timeMs); + } + } + + @Override + public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) { + if (DEBUG) { + Log.d(TAG, "onTimeShiftCurrentPositionChanged()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onTimeShiftCurrentPositionChanged - session not created"); + return; + } + for (TimeShiftPositionCallback callback : mTimeShiftPositionCallbacks) { + callback.onTimeShiftCurrentPositionChanged(mInputId, timeMs); + } + } } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 5375bfc..152370a 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -1341,6 +1341,108 @@ public final class TvInputManagerService extends SystemService { } @Override + public void timeShiftPause(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftPause"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftPause(); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftPause", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void timeShiftResume(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftResume"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftResume(); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftResume", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void timeShiftSeekTo(IBinder sessionToken, long timeMs, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftSeekTo"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftSeekTo(timeMs); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftSeekTo", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void timeShiftSetPlaybackRate(IBinder sessionToken, float rate, int audioMode, + int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftSetPlaybackRate"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftSetPlaybackRate(rate, audioMode); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftSetPlaybackRate", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void timeShiftTrackCurrentPosition(IBinder sessionToken, boolean enabled, + int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftTrackCurrentPosition"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .timeShiftTrackCurrentPosition(enabled); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftTrackCurrentPosition", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public List<TvInputHardwareInfo> getHardwareList() throws RemoteException { if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE) != PackageManager.PERMISSION_GRANTED) { @@ -2144,6 +2246,58 @@ public final class TvInputManagerService extends SystemService { } } } + + @Override + public void onTimeShiftStatusChanged(int status) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTimeShiftStatusChanged()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onTimeShiftStatusChanged(status, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTimeShiftStatusChanged", e); + } + } + } + + @Override + public void onTimeShiftStartPositionChanged(long timeMs) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTimeShiftStartPositionChanged()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onTimeShiftStartPositionChanged(timeMs, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTimeShiftStartPositionChanged", e); + } + } + } + + @Override + public void onTimeShiftCurrentPositionChanged(long timeMs) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTimeShiftCurrentPositionChanged()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onTimeShiftCurrentPositionChanged(timeMs, + mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTimeShiftCurrentPositionChanged", e); + } + } + } } private static final class WatchLogHandler extends Handler { |
