diff options
| -rw-r--r-- | api/current.txt | 12 | ||||
| -rw-r--r-- | api/system-current.txt | 12 | ||||
| -rw-r--r-- | media/java/android/media/tv/ITvInputManager.aidl | 2 | ||||
| -rw-r--r-- | media/java/android/media/tv/ITvInputSession.aidl | 2 | ||||
| -rw-r--r-- | media/java/android/media/tv/ITvInputSessionWrapper.java | 12 | ||||
| -rw-r--r-- | media/java/android/media/tv/TvInputManager.java | 78 | ||||
| -rw-r--r-- | media/java/android/media/tv/TvInputService.java | 229 | ||||
| -rw-r--r-- | media/java/android/media/tv/TvView.java | 90 | ||||
| -rw-r--r-- | services/core/java/com/android/server/tv/TvInputManagerService.java | 8 |
9 files changed, 240 insertions, 205 deletions
diff --git a/api/current.txt b/api/current.txt index b6380d757ace..b6a630ba429f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -17519,9 +17519,10 @@ package android.media.tv { 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 TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3 + field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2 + field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0 + field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 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 @@ -17556,7 +17557,6 @@ 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>); @@ -17575,6 +17575,7 @@ package android.media.tv { method public abstract boolean onSetSurface(android.view.Surface); method public void onSurfaceChanged(int, int, int); method public long onTimeShiftGetCurrentPosition(); + method public long onTimeShiftGetStartPosition(); method public void onTimeShiftPause(); method public void onTimeShiftResume(); method public void onTimeShiftSeekTo(long); @@ -17627,19 +17628,18 @@ 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 setTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback); 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 { diff --git a/api/system-current.txt b/api/system-current.txt index a65c35d899aa..318d1dbd8002 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -18882,9 +18882,10 @@ package android.media.tv { 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 TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3 + field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2 + field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0 + field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 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 @@ -18972,7 +18973,6 @@ 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>); @@ -18994,6 +18994,7 @@ package android.media.tv { method public abstract boolean onSetSurface(android.view.Surface); method public void onSurfaceChanged(int, int, int); method public long onTimeShiftGetCurrentPosition(); + method public long onTimeShiftGetStartPosition(); method public void onTimeShiftPause(); method public void onTimeShiftResume(); method public void onTimeShiftSeekTo(long); @@ -19070,7 +19071,6 @@ 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); @@ -19080,6 +19080,7 @@ package android.media.tv { method public void setMain(); method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener); method public void setStreamVolume(float); + method public void setTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback); method public void setZOrderMediaOverlay(boolean); method public void setZOrderOnTop(boolean); method public void timeShiftPause(); @@ -19088,7 +19089,6 @@ package android.media.tv { 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 { diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index f96469e694e1..b6491d8adf22 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -78,7 +78,7 @@ interface ITvInputManager { 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); + void timeShiftEnablePositionTracking(in IBinder sessionToken, boolean enable, int userId); // For TV input hardware binding List<TvInputHardwareInfo> getHardwareList(); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 306abb846bec..a054200f8a2e 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -51,5 +51,5 @@ oneway interface ITvInputSession { void timeShiftResume(); void timeShiftSeekTo(long timeMs); void timeShiftSetPlaybackRate(float rate, int audioMode); - void timeShiftTrackCurrentPosition(boolean enabled); + void timeShiftEnablePositionTracking(boolean enable); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index f22a8fc638d0..a3442e36e66e 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -61,7 +61,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand 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 static final int DO_TIME_SHIFT_ENABLE_POSITION_TRACKING = 18; private final HandlerCaller mCaller; @@ -174,8 +174,8 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.timeShiftSetPlaybackRate((Float) msg.obj, msg.arg1); break; } - case DO_TIME_SHIFT_TRACK_CURRENT_POSITION: { - mTvInputSessionImpl.timeShiftTrackCurrentPosition((Boolean) msg.obj); + case DO_TIME_SHIFT_ENABLE_POSITION_TRACKING: { + mTvInputSessionImpl.timeShiftEnablePositionTracking((Boolean) msg.obj); break; } default: { @@ -290,9 +290,9 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override - public void timeShiftTrackCurrentPosition(boolean enabled) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TIME_SHIFT_TRACK_CURRENT_POSITION, - Boolean.valueOf(enabled))); + public void timeShiftEnablePositionTracking(boolean enable) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO( + DO_TIME_SHIFT_ENABLE_POSITION_TRACKING, Boolean.valueOf(enable))); } private final class TvInputEventReceiver extends InputEventReceiver { diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index a4d8174eb278..3c67ea06dead 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -18,6 +18,7 @@ package android.media.tv; import android.annotation.SystemApi; import android.graphics.Rect; +import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -72,24 +73,29 @@ 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; + private static final int TIME_SHIFT_STATUS_END = 3; /** - * Time shifting is available. In this status, the application can pause/resume the playback, - * seek to a specific position, and change the playback rate. + * Status prior to calling {@link TvInputService.Session#notifyTimeShiftStatusChanged}. */ - public static final int TIME_SHIFT_STATUS_AVAILABLE = TIME_SHIFT_STATUS_START; + public static final int TIME_SHIFT_STATUS_UNKNOWN = TIME_SHIFT_STATUS_START; /** - * Time shifting is not available. + * The TV input does not support time shifting. */ - public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 1; + public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; /** - * An error occurred while handling a time shift request. To recover the status, tune to a - * new channel. + * Time shifting is currently not available but might work again later. */ - public static final int TIME_SHIFT_STATUS_ERROR = TIME_SHIFT_STATUS_END; + public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; + + /** + * Time shifting is currently available. In this status, the application assumes it can + * pause/resume playback, seek to a specified time position and set playback rate and audio + * mode. + */ + public static final int TIME_SHIFT_STATUS_AVAILABLE = TIME_SHIFT_STATUS_END; public static final long TIME_SHIFT_INVALID_TIME = Long.MIN_VALUE; @@ -352,35 +358,41 @@ public final class TvInputManager { } /** - * This is called when the trick play status is changed. + * This is called when the time shift status is changed. * * @param session A {@link TvInputManager.Session} associated with this callback. - * @param status The current time shift status: + * @param status The current time shift status. Should be one of the followings. * <ul> - * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} - * <li>{@link TvInputManager#TIME_SHIFT_STATUS_ERROR} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} * </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. + * This is called when the start playback position is changed. + * <p> + * The start playback position of the time shifted program should be adjusted when the TV + * input cannot retain the whole recorded program due to some reason (e.g. limitation on + * storage space). This is necessary to prevent the application from allowing the user to + * seek to a time position that is not reachable. + * </p> * * @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. + * @param timeMs The start playback position of the time shifted program, in milliseconds + * since the epoch. */ public void onTimeShiftStartPositionChanged(Session session, long timeMs) { } /** - * This is called when the current position is changed. + * This is called when the current playback position is changed. * * @param session A {@link TvInputManager.Session} associated with this callback. - * @param timeMs The current position, in milliseconds since the epoch. + * @param timeMs The current playback position of the time shifted program, in milliseconds + * since the epoch. */ public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) { } @@ -893,7 +905,7 @@ public final class TvInputManager { } } } catch (RemoteException e) { - Log.e(TAG, "TvInputManager initialization failed: " + e); + Log.e(TAG, "TvInputManager initialization failed", e); } } @@ -1677,12 +1689,13 @@ public final class TvInputManager { } /** - * 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. + * Seeks to a specified time position. + * <p> + * Normally, the position is given within range between the start and the current time, + * inclusively. * - * @param timeMs The target time, in milliseconds since the epoch. + * @param timeMs The time position to seek to, in milliseconds since the epoch. + * @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged */ void timeShiftSeekTo(long timeMs) { if (mToken == null) { @@ -1697,10 +1710,10 @@ public final class TvInputManager { } /** - * Sets a playback rate and an audio mode. + * Sets playback rate and 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: + * @param audioMode Audio playback mode. Must be one of the supported audio modes: * <ul> * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} * </ul> @@ -1710,6 +1723,9 @@ public final class TvInputManager { Log.w(TAG, "The session has been already released"); return; } + if (audioMode != MediaPlayer.PLAYBACK_RATE_AUDIO_MODE_RESAMPLE) { + throw new IllegalArgumentException("Unknown audio playback mode " + audioMode); + } try { mService.timeShiftSetPlaybackRate(mToken, rate, audioMode, mUserId); } catch (RemoteException e) { @@ -1718,15 +1734,17 @@ public final class TvInputManager { } /** - * Returns the current playback position. + * Enable/disable position tracking. + * + * @param enable {@code true} to enable tracking, {@code false} otherwise. */ - void timeShiftTrackCurrentPosition(boolean enabled) { + void timeShiftEnablePositionTracking(boolean enable) { if (mToken == null) { Log.w(TAG, "The session has been already released"); return; } try { - mService.timeShiftTrackCurrentPosition(mToken, enabled, mUserId); + mService.timeShiftEnablePositionTracking(mToken, enable, mUserId); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 93abc2b3138c..4e7aaa056b99 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -250,10 +250,10 @@ public abstract class TvInputService extends Service { private boolean mOverlayViewEnabled; private IBinder mWindowToken; private Rect mOverlayFrame; + private long mStartPositionMs; private long mCurrentPositionMs; - private final TimeShiftCurrentPositionTrackingRunnable - mTimeShiftCurrentPositionTrackingRunnable = - new TimeShiftCurrentPositionTrackingRunnable(); + private final TimeShiftPositionTrackingRunnable + mTimeShiftPositionTrackingRunnable = new TimeShiftPositionTrackingRunnable(); private final Object mLock = new Object(); // @GuardedBy("mLock") @@ -320,7 +320,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onSessionEvent(eventType, eventArgs); } } catch (RemoteException e) { - Log.w(TAG, "error in sending event (event=" + eventType + ")"); + Log.w(TAG, "error in sending event (event=" + eventType + ")", e); } } }); @@ -341,7 +341,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onChannelRetuned(channelUri); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyChannelRetuned"); + Log.w(TAG, "error in notifyChannelRetuned", e); } } }); @@ -380,7 +380,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onTracksChanged(tracks); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyTracksChanged"); + Log.w(TAG, "error in notifyTracksChanged", e); } } }); @@ -410,7 +410,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onTrackSelected(type, trackId); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyTrackSelected"); + Log.w(TAG, "error in notifyTrackSelected", e); } } }); @@ -433,7 +433,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onVideoAvailable(); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyVideoAvailable"); + Log.w(TAG, "error in notifyVideoAvailable", e); } } }); @@ -467,7 +467,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onVideoUnavailable(reason); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyVideoUnavailable"); + Log.w(TAG, "error in notifyVideoUnavailable", e); } } }); @@ -508,7 +508,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onContentAllowed(); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyContentAllowed"); + Log.w(TAG, "error in notifyContentAllowed", e); } } }); @@ -550,30 +550,36 @@ public abstract class TvInputService extends Service { mSessionCallback.onContentBlocked(rating.flattenToString()); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyContentBlocked"); + Log.w(TAG, "error in notifyContentBlocked", e); } } }); } /** - * Informs the application that the trick play status is changed. + * Informs the application that the time shift 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. + * Prior to calling this method, the application assumes the status + * {@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN}. Right after the session is created, it + * is important to invoke the method with the status + * {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} if the implementation does support + * time shifting, or {@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} otherwise. Failure + * to notifying the current status change immediately might result in an undesirable + * behavior in the application such as hiding the play controls. + * </p><p> + * If the status {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} is reported, the + * application assumes it can pause/resume playback, seek to a specified time position and + * set playback rate and audio mode. The implementation should override + * {@link #onTimeShiftPause}, {@link #onTimeShiftResume}, {@link #onTimeShiftSeekTo}, + * {@link #onTimeShiftGetStartPosition}, {@link #onTimeShiftGetCurrentPosition} and + * {@link #onTimeShiftSetPlaybackRate}. * </p> * - * @param status The current time shift status: + * @param status The current time shift status. Should be one of the followings. * <ul> - * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} - * <li>{@link TvInputManager#TIME_SHIFT_STATUS_ERROR} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} * </ul> */ public void notifyTimeShiftStatusChanged(final int status) { @@ -586,23 +592,13 @@ public abstract class TvInputService extends Service { mSessionCallback.onTimeShiftStatusChanged(status); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyTimeShiftStatusChanged"); + Log.w(TAG, "error in notifyTimeShiftStatusChanged", e); } } }); } - /** - * 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) { + private void notifyTimeShiftStartPositionChanged(final long timeMs) { executeOrPostRunnable(new Runnable() { @Override public void run() { @@ -612,17 +608,12 @@ public abstract class TvInputService extends Service { mSessionCallback.onTimeShiftStartPositionChanged(timeMs); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyTimeShiftStartPositionChanged"); + Log.w(TAG, "error in notifyTimeShiftStartPositionChanged", e); } } }); } - /** - * 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 @@ -633,7 +624,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onTimeShiftCurrentPositionChanged(timeMs); } } catch (RemoteException e) { - Log.w(TAG, "error in notifyTimeShiftCurrentPositionChanged"); + Log.w(TAG, "error in notifyTimeShiftCurrentPositionChanged", e); } } }); @@ -666,7 +657,7 @@ public abstract class TvInputService extends Service { mSessionCallback.onLayoutSurface(left, top, right, bottom); } } catch (RemoteException e) { - Log.w(TAG, "error in layoutSurface"); + Log.w(TAG, "error in layoutSurface", e); } } }); @@ -711,7 +702,7 @@ public abstract class TvInputService extends Service { * When {@code setSurface(null)} is called, the implementation should stop using the Surface * object previously given and release any references to it. * - * @param surface possibly {@code null} {@link Surface} an application passes to this TV + * @param surface possibly {@code null} {@link Surface} the application passes to this TV * input session. * @return {@code true} if the surface was set, {@code false} otherwise. */ @@ -730,10 +721,10 @@ public abstract class TvInputService extends Service { } /** - * Called when a size of an overlay view is changed by an application. Even when the overlay - * view is disabled by {@link #setOverlayViewEnabled}, this is called. The size is same as - * the size of {@link Surface} in general. Once {@link #layoutSurface} is called, the sizes - * of {@link Surface} and the overlay view can be different. + * Called when a size of an overlay view is changed by the application. Even when the + * overlay view is disabled by {@link #setOverlayViewEnabled}, this is called. The size is + * same as the size of {@link Surface} in general. Once {@link #layoutSurface} is called, + * the sizes of {@link Surface} and the overlay view can be different. * * @param width The width of the overlay view. * @param height The height of the overlay view. @@ -836,7 +827,7 @@ public abstract class TvInputService extends Service { } /** - * Called when an application requests to create an overlay view. Each session + * Called when the application requests to create an overlay view. Each session * implementation can override this method and return its own view. * * @return a view attached to the overlay window @@ -846,66 +837,94 @@ public abstract class TvInputService extends Service { } /** - * Called when an application requests to pause the playback. + * Called when the application requests to pause playback. * - * @see #onTimeShiftResume() - * @see #onTimeShiftSeekTo(long) - * @see #onTimeShiftSetPlaybackRate(float, int) - * @see #onTimeShiftGetCurrentPosition() + * @see #onTimeShiftResume + * @see #onTimeShiftSeekTo + * @see #onTimeShiftSetPlaybackRate + * @see #onTimeShiftGetStartPosition + * @see #onTimeShiftGetCurrentPosition */ public void onTimeShiftPause() { } /** - * Called when an application requests to resume the playback. + * Called when the application requests to resume playback. * - * @see #onTimeShiftPause() - * @see #onTimeShiftSeekTo(long) - * @see #onTimeShiftSetPlaybackRate(float, int) - * @see #onTimeShiftGetCurrentPosition() + * @see #onTimeShiftPause + * @see #onTimeShiftSeekTo + * @see #onTimeShiftSetPlaybackRate + * @see #onTimeShiftGetStartPosition + * @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. + * Called when the application requests to seek to a specified time position. Normally, the + * position is given within range between the start and the current time, inclusively. The + * implementation is expected to seek to the nearest time position if the given position is + * not in the range. * - * @param timeMs The target time, in milliseconds since the epoch - * @see #onTimeShiftResume() - * @see #onTimeShiftPause() - * @see #onTimeShiftSetPlaybackRate(float, int) - * @see #onTimeShiftGetCurrentPosition() + * @param timeMs The time position to seek to, in milliseconds since the epoch. + * @see #onTimeShiftResume + * @see #onTimeShiftPause + * @see #onTimeShiftSetPlaybackRate + * @see #onTimeShiftGetStartPosition + * @see #onTimeShiftGetCurrentPosition */ public void onTimeShiftSeekTo(long timeMs) { } /** - * Called when an application sets a playback rate and an audio mode. + * Called when the application sets playback rate and 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: + * @param audioMode 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() + * @see #onTimeShiftResume + * @see #onTimeShiftPause + * @see #onTimeShiftSeekTo + * @see #onTimeShiftGetStartPosition + * @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. + * Returns the start playback position for time shifting, in milliseconds since the epoch. + * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the + * moment. + * <p> + * The start playback position of the time shifted program should be adjusted when the + * implementation cannot retain the whole recorded program due to some reason (e.g. + * limitation on storage space). It is the earliest possible time position that the user can + * seek to, thus failure to notifying its change immediately might result in bad experience + * where the application allows the user to seek to an invalid time position. + * </p> + * + * @see #onTimeShiftResume + * @see #onTimeShiftPause + * @see #onTimeShiftSeekTo + * @see #onTimeShiftSetPlaybackRate + * @see #onTimeShiftGetCurrentPosition + */ + public long onTimeShiftGetStartPosition() { + return TvInputManager.TIME_SHIFT_INVALID_TIME; + } + + /** + * Returns the current playback position for time shifting, in milliseconds since the epoch. + * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the + * moment. * - * @see #onTimeShiftResume() - * @see #onTimeShiftPause() - * @see #onTimeShiftSeekTo(long) - * @see #onTimeShiftSetPlaybackRate(float, int) + * @see #onTimeShiftResume + * @see #onTimeShiftPause + * @see #onTimeShiftSeekTo + * @see #onTimeShiftSetPlaybackRate + * @see #onTimeShiftGetStartPosition */ public long onTimeShiftGetCurrentPosition() { return TvInputManager.TIME_SHIFT_INVALID_TIME; @@ -1043,7 +1062,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); + mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable); } /** @@ -1125,7 +1144,7 @@ public abstract class TvInputService extends Service { * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach * to the overlay window. * - * @param windowToken A window token of an application. + * @param windowToken A window token of the application. * @param frame A position of the overlay view. */ void createOverlayView(IBinder windowToken, Rect frame) { @@ -1245,13 +1264,16 @@ public abstract class TvInputService extends Service { } /** - * Turns on/off the current position tracking. + * Enable/disable position tracking. + * + * @param enable {@code true} to enable tracking, {@code false} otherwise. */ - void timeShiftTrackCurrentPosition(boolean enabled) { - if (enabled) { - mHandler.post(mTimeShiftCurrentPositionTrackingRunnable); + void timeShiftEnablePositionTracking(boolean enable) { + if (enable) { + mHandler.post(mTimeShiftPositionTrackingRunnable); } else { - mHandler.removeCallbacks(mTimeShiftCurrentPositionTrackingRunnable); + mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable); + mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; } } @@ -1283,7 +1305,7 @@ public abstract class TvInputService extends Service { } isNavigationKey = isNavigationKey(keyEvent.getKeyCode()); // When media keys and KEYCODE_MEDIA_AUDIO_TRACK are dispatched to ViewRootImpl, - // ViewRootImpl always consumes the keys. In this case, an application loses + // ViewRootImpl always consumes the keys. In this case, the application loses // a chance to handle media keys. Therefore, media keys are not dispatched to // ViewRootImpl. skipDispatchToOverlayView = KeyEvent.isMediaKey(keyEvent.getKeyCode()) @@ -1352,16 +1374,21 @@ public abstract class TvInputService extends Service { } } - private final class TimeShiftCurrentPositionTrackingRunnable implements Runnable { + private final class TimeShiftPositionTrackingRunnable implements Runnable { @Override public void run() { - long pos = onTimeShiftGetCurrentPosition(); - if (mCurrentPositionMs != pos) { - mCurrentPositionMs = pos; - notifyTimeShiftCurrentPositionChanged(pos); + long startPositionMs = onTimeShiftGetStartPosition(); + if (mStartPositionMs != startPositionMs) { + mStartPositionMs = startPositionMs; + notifyTimeShiftStartPositionChanged(startPositionMs); + } + long currentPositionMs = onTimeShiftGetCurrentPosition(); + if (mCurrentPositionMs != currentPositionMs) { + mCurrentPositionMs = currentPositionMs; + notifyTimeShiftCurrentPositionChanged(currentPositionMs); } - mHandler.removeCallbacks(mTimeShiftCurrentPositionTrackingRunnable); - mHandler.postDelayed(mTimeShiftCurrentPositionTrackingRunnable, + mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable); + mHandler.postDelayed(mTimeShiftPositionTrackingRunnable, POSITION_UPDATE_INTERVAL_MS); } } @@ -1538,7 +1565,7 @@ public abstract class TvInputService extends Service { try { mCallbacks.getBroadcastItem(i).addHardwareTvInput(deviceId, inputInfo); } catch (RemoteException e) { - Log.e(TAG, "Error while broadcasting.", e); + Log.e(TAG, "error in broadcastAddHardwareTvInput", e); } } mCallbacks.finishBroadcast(); @@ -1550,7 +1577,7 @@ public abstract class TvInputService extends Service { try { mCallbacks.getBroadcastItem(i).addHdmiTvInput(id, inputInfo); } catch (RemoteException e) { - Log.e(TAG, "Error while broadcasting.", e); + Log.e(TAG, "error in broadcastAddHdmiTvInput", e); } } mCallbacks.finishBroadcast(); @@ -1562,7 +1589,7 @@ public abstract class TvInputService extends Service { try { mCallbacks.getBroadcastItem(i).removeTvInput(inputId); } catch (RemoteException e) { - Log.e(TAG, "Error while broadcasting.", e); + Log.e(TAG, "error in broadcastRemoveTvInput", e); } } mCallbacks.finishBroadcast(); @@ -1583,7 +1610,7 @@ public abstract class TvInputService extends Service { // Failed to create a session. cb.onSessionCreated(null, null); } catch (RemoteException e) { - Log.e(TAG, "error in onSessionCreated"); + Log.e(TAG, "error in onSessionCreated", e); } return; } @@ -1604,7 +1631,7 @@ public abstract class TvInputService extends Service { try { cb.onSessionCreated(null, null); } catch (RemoteException e) { - Log.e(TAG, "error in onSessionCreated"); + Log.e(TAG, "error in onSessionCreated", e); } return; } @@ -1635,7 +1662,7 @@ public abstract class TvInputService extends Service { try { cb.onSessionCreated(stub, hardwareSessionToken); } catch (RemoteException e) { - Log.e(TAG, "error in onSessionCreated"); + Log.e(TAG, "error in onSessionCreated", e); } if (sessionImpl != null) { sessionImpl.initialize(cb); diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 115d094d6d55..42c2cd76062e 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -42,7 +42,6 @@ import android.view.ViewGroup; import android.view.ViewRootImpl; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.List; /** @@ -104,7 +103,7 @@ public class TvView extends ViewGroup { private int mSurfaceViewRight; private int mSurfaceViewTop; private int mSurfaceViewBottom; - private List<TimeShiftPositionCallback> mTimeShiftPositionCallbacks = new ArrayList<>(); + private TimeShiftPositionCallback mTimeShiftPositionCallback; private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { @Override @@ -175,8 +174,8 @@ public class TvView extends ViewGroup { /** * Sets the callback to be invoked when an event is dispatched to this TvView. * - * @param callback The callback to receive events. A value of {@code null} removes any existing - * callbacks. + * @param callback The callback to receive events. A value of {@code null} removes the existing + * callback. */ public void setCallback(TvInputCallback callback) { mCallback = callback; @@ -422,7 +421,7 @@ public class TvView extends ViewGroup { } /** - * Pauses the playback. Call {@link #timeShiftResume()} to restart the playback. + * Pauses playback. No-op if it is already paused. Call {@link #timeShiftResume} to resume. */ public void timeShiftPause() { if (mSession != null) { @@ -431,7 +430,7 @@ public class TvView extends ViewGroup { } /** - * Resumes the playback. No-op if it is already playing the channel. + * Resumes playback. No-op if it is already resumed. Call {@link #timeShiftPause} to pause. */ public void timeShiftResume() { if (mSession != null) { @@ -440,11 +439,11 @@ public class TvView extends ViewGroup { } /** - * 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. + * Seeks to a specified time position. {@code timeMs} must be equal to or greater than the start + * position returned by {@link TimeShiftPositionCallback#onTimeShiftStartPositionChanged} and + * equal to or less than the current time. * - * @param timeMs The target time, in milliseconds since the epoch. + * @param timeMs The time position to seek to, in milliseconds since the epoch. */ public void timeShiftSeekTo(long timeMs) { if (mSession != null) { @@ -453,10 +452,10 @@ public class TvView extends ViewGroup { } /** - * Sets a playback rate and an audio mode. + * Sets playback rate and 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: + * @param audioMode Audio playback mode. Must be one of the supported audio modes: * <ul> * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE} * </ul> @@ -468,36 +467,21 @@ public class TvView extends ViewGroup { } /** - * Registers a {@link TvView.TimeShiftPositionCallback}. + * Sets the callback to be invoked when the time shift position is changed. * - * @param callback A callback used to monitor the time shift range and current position. + * @param callback The callback to receive time shift position changes. A value of {@code null} + * removes the existing callback. */ - public void registerTimeShiftPositionCallback(TimeShiftPositionCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback can not be null."); - } - mTimeShiftPositionCallbacks.add(callback); - ensureCurrentPositionTracking(); + public void setTimeShiftPositionCallback(TimeShiftPositionCallback callback) { + mTimeShiftPositionCallback = callback; + ensurePositionTracking(); } - /** - * 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() { + private void ensurePositionTracking() { if (mSession == null) { return; } - mSession.timeShiftTrackCurrentPosition(!mTimeShiftPositionCallbacks.isEmpty()); + mSession.timeShiftEnablePositionTracking(mTimeShiftPositionCallback != null); } /** @@ -810,17 +794,22 @@ public class TvView extends ViewGroup { } /** - * Callback used to receive the information on the possible range for time shifting and currrent - * position. + * Callback used to receive time shift position changes. */ 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. + * This is called when the start playback position is changed. + * <p> + * The start playback position of the time shifted program can be adjusted by the TV input + * when it cannot retain the whole recorded program due to some reason (e.g. limitation on + * storage space). The application should not allow the user to seek to a position earlier + * than the start position. + * </p> * * @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. + * @param timeMs The start playback position of the time shifted program, in milliseconds + * since the epoch. */ public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { } @@ -829,7 +818,8 @@ public class TvView extends ViewGroup { * 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. + * @param timeMs The current playback position of the time shifted program, in milliseconds + * since the epoch. */ public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { } @@ -958,11 +948,11 @@ public class TvView extends ViewGroup { * 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: + * @param status The current time shift status. Should be one of the followings. * <ul> - * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} - * <li>{@link TvInputManager#TIME_SHIFT_STATUS_ERROR} + * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} * </ul> */ public void onTimeShiftStatusChanged(String inputId, int status) { @@ -1040,7 +1030,7 @@ public class TvView extends ViewGroup { mAppPrivateCommandAction = null; mAppPrivateCommandData = null; } - ensureCurrentPositionTracking(); + ensurePositionTracking(); } else { mSessionCallback = null; if (mCallback != null) { @@ -1234,8 +1224,8 @@ public class TvView extends ViewGroup { Log.w(TAG, "onTimeShiftStartPositionChanged - session not created"); return; } - for (TimeShiftPositionCallback callback : mTimeShiftPositionCallbacks) { - callback.onTimeShiftStartPositionChanged(mInputId, timeMs); + if (mTimeShiftPositionCallback != null) { + mTimeShiftPositionCallback.onTimeShiftStartPositionChanged(mInputId, timeMs); } } @@ -1248,8 +1238,8 @@ public class TvView extends ViewGroup { Log.w(TAG, "onTimeShiftCurrentPositionChanged - session not created"); return; } - for (TimeShiftPositionCallback callback : mTimeShiftPositionCallbacks) { - callback.onTimeShiftCurrentPositionChanged(mInputId, timeMs); + if (mTimeShiftPositionCallback != null) { + mTimeShiftPositionCallback.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 152370af27a8..9b4b5228b809 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -1422,19 +1422,19 @@ public final class TvInputManagerService extends SystemService { } @Override - public void timeShiftTrackCurrentPosition(IBinder sessionToken, boolean enabled, + public void timeShiftEnablePositionTracking(IBinder sessionToken, boolean enable, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "timeShiftTrackCurrentPosition"); + userId, "timeShiftEnablePositionTracking"); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { try { getSessionLocked(sessionToken, callingUid, resolvedUserId) - .timeShiftTrackCurrentPosition(enabled); + .timeShiftEnablePositionTracking(enable); } catch (RemoteException | SessionNotFoundException e) { - Slog.e(TAG, "error in timeShiftTrackCurrentPosition", e); + Slog.e(TAG, "error in timeShiftEnablePositionTracking", e); } } } finally { |