TIF: Add time shift APIs.
Change-Id: I4faecd99d9e565c7228fc4b9da99e2adf1e67f19
diff --git a/api/current.txt b/api/current.txt
index 202139c..dc2bb40 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -17465,6 +17465,10 @@
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
@@ -17499,6 +17503,8 @@
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();
@@ -17515,6 +17521,11 @@
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);
@@ -17563,19 +17574,31 @@
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);
@@ -17583,6 +17606,7 @@
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 8d8e0c3..11d705f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -18825,6 +18825,10 @@
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
@@ -18867,6 +18871,9 @@
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);
@@ -18909,6 +18916,8 @@
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();
@@ -18928,6 +18937,11 @@
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);
@@ -19000,6 +19014,7 @@
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);
@@ -19011,14 +19026,25 @@
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);
@@ -19027,6 +19053,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
/**
* 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 @@
* 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 @@
* 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 @@
* 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 @@
@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 @@
}
});
}
+
+ 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 @@
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 @@
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 @@
Log.w(TAG, "The session has been already released");
return;
}
- synchronized (mTrackLock) {
+ synchronized (mMetadataLock) {
mAudioTracks.clear();
mVideoTracks.clear();
mSubtitleTracks.clear();
@@ -1367,7 +1486,7 @@
* @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 @@
* @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 @@
* @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 @@
* there is an update.
*/
boolean updateTracks(List<TvTrackInfo> tracks) {
- synchronized (mTrackLock) {
+ synchronized (mMetadataLock) {
mAudioTracks.clear();
mVideoTracks.clear();
mSubtitleTracks.clear();
@@ -1485,7 +1604,7 @@
* 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 @@
* 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 @@
}
/**
+ * 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 @@
* 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 @@
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 @@
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mHandler = new Handler(context.getMainLooper());
+ mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
}
/**
@@ -550,6 +557,89 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
// 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 @@
* 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 @@
}
/**
+ * 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 @@
}
}
+ 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.ViewRootImpl;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -103,6 +104,7 @@
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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
/**
* 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 @@
@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 @@
mAppPrivateCommandAction = null;
mAppPrivateCommandData = null;
}
+ ensureCurrentPositionTracking();
} else {
mSessionCallback = null;
if (mCallback != null) {
@@ -1087,5 +1210,47 @@
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 @@
}
@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 @@
}
}
}
+
+ @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 {