diff --git a/api/current.txt b/api/current.txt index 081805587f478..258a294188a14 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16140,6 +16140,7 @@ package android.media.tv { public abstract class TvInputService.Session implements android.view.KeyEvent.Callback { ctor public TvInputService.Session(); method public void dispatchChannelRetuned(android.net.Uri); + method public void dispatchTrackInfoChanged(java.util.List); method public android.view.View onCreateOverlayView(); method public boolean onGenericMotionEvent(android.view.MotionEvent); method public boolean onKeyDown(int, android.view.KeyEvent); @@ -16147,26 +16148,61 @@ package android.media.tv { method public boolean onKeyMultiple(int, int, android.view.KeyEvent); method public boolean onKeyUp(int, android.view.KeyEvent); method public abstract void onRelease(); + method public boolean onSelectTrack(android.media.tv.TvTrackInfo); method public abstract void onSetStreamVolume(float); method public abstract boolean onSetSurface(android.view.Surface); method public boolean onTouchEvent(android.view.MotionEvent); method public boolean onTrackballEvent(android.view.MotionEvent); method public abstract boolean onTune(android.net.Uri); + method public boolean onUnselectTrack(android.media.tv.TvTrackInfo); method public void setOverlayViewEnabled(boolean); } + public final class TvTrackInfo implements android.os.Parcelable { + method public boolean containsKey(java.lang.String); + method public int describeContents(); + method public boolean getBoolean(java.lang.String); + method public int getInt(java.lang.String); + method public java.lang.String getString(java.lang.String); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final java.lang.String KEY_CHANNEL_COUNT = "channel-count"; + field public static final java.lang.String KEY_HEIGHT = "height"; + field public static final java.lang.String KEY_IS_SELECTED = "is-selected"; + field public static final java.lang.String KEY_LANGUAGE = "language"; + field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate"; + field public static final java.lang.String KEY_TAG = "tag"; + field public static final java.lang.String KEY_TYPE = "type"; + field public static final java.lang.String KEY_WIDTH = "width"; + field public static final int VALUE_TYPE_AUDIO = 0; // 0x0 + field public static final int VALUE_TYPE_SUBTITLE = 2; // 0x2 + field public static final int VALUE_TYPE_VIDEO = 1; // 0x1 + } + + public static final class TvTrackInfo.Builder { + ctor public TvTrackInfo.Builder(int, java.lang.String, boolean); + ctor public TvTrackInfo.Builder(android.media.tv.TvTrackInfo); + method public android.media.tv.TvTrackInfo build(); + method public android.media.tv.TvTrackInfo.Builder putBoolean(java.lang.String, boolean); + method public android.media.tv.TvTrackInfo.Builder putInt(java.lang.String, int); + method public android.media.tv.TvTrackInfo.Builder putString(java.lang.String, java.lang.String); + } + public class TvView extends android.view.ViewGroup { ctor public TvView(android.content.Context); ctor public TvView(android.content.Context, android.util.AttributeSet); ctor public TvView(android.content.Context, android.util.AttributeSet, int); method public boolean dispatchUnhandledInputEvent(android.view.InputEvent); + method public java.util.List getTracks(); method protected void onLayout(boolean, int, int, int, int); method public boolean onUnhandledInputEvent(android.view.InputEvent); method public void reset(); + method public void selectTrack(android.media.tv.TvTrackInfo); method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener); method public void setStreamVolume(float); method public void setTvInputListener(android.media.tv.TvView.TvInputListener); method public void tune(java.lang.String, android.net.Uri); + method public void unselectTrack(android.media.tv.TvTrackInfo); field public static final int ERROR_BUSY = 0; // 0x0 field public static final int ERROR_TV_INPUT_DISCONNECTED = 1; // 0x1 } @@ -16179,6 +16215,8 @@ package android.media.tv { ctor public TvView.TvInputListener(); method public void onChannelRetuned(java.lang.String, android.net.Uri); method public void onError(java.lang.String, int); + method public void onTrackInfoChanged(java.lang.String, java.util.List); + method public void onVideoSizeChanged(java.lang.String, int, int); } } diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index 285400794bf9e..27360eda478c7 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -19,6 +19,7 @@ package android.media.tv; import android.content.ComponentName; import android.media.tv.ITvInputSession; import android.net.Uri; +import android.media.tv.TvTrackInfo; import android.os.Bundle; import android.view.InputChannel; @@ -32,8 +33,6 @@ oneway interface ITvInputClient { void onAvailabilityChanged(in String inputId, boolean isAvailable); void onSessionReleased(int seq); void onSessionEvent(in String name, in Bundle args, int seq); - void onVideoStreamChanged(int width, int height, boolean interlaced, int seq); - void onAudioStreamChanged(int channelCount, int seq); - void onClosedCaptionStreamChanged(boolean hasClosedCaption, int seq); void onChannelRetuned(in Uri channelUri, int seq); + void onTrackInfoChanged(in List tracks, int seq); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 6db5a18d9d349..bdebd7c592d29 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -23,6 +23,7 @@ import android.media.tv.ITvInputHardwareCallback; import android.media.tv.ITvInputClient; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; +import android.media.tv.TvTrackInfo; import android.net.Uri; import android.view.Surface; @@ -44,6 +45,8 @@ interface ITvInputManager { void setSurface(in IBinder sessionToken, in Surface surface, int userId); void setVolume(in IBinder sessionToken, float volume, int userId); void tune(in IBinder sessionToken, in Uri channelUri, int userId); + void selectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId); + void unselectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId); void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame, int userId); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index fb2e2510dbd0d..f8590f101bdae 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -17,6 +17,7 @@ package android.media.tv; import android.graphics.Rect; +import android.media.tv.TvTrackInfo; import android.net.Uri; import android.view.Surface; @@ -32,6 +33,8 @@ oneway interface ITvInputSession { // is to introduce some new concepts that will solve a number of problems in audio policy today. void setVolume(float volume); void tune(in Uri channelUri); + void selectTrack(in TvTrackInfo track); + void unselectTrack(in TvTrackInfo track); void createOverlayView(in IBinder windowToken, in Rect frame); void relayoutOverlayView(in Rect frame); diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index 5a57ccd8e95ae..4d7205916b45d 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -18,6 +18,7 @@ package android.media.tv; import android.media.tv.ITvInputSession; import android.net.Uri; +import android.media.tv.TvTrackInfo; import android.os.Bundle; /** @@ -28,8 +29,6 @@ import android.os.Bundle; oneway interface ITvInputSessionCallback { void onSessionCreated(ITvInputSession session); void onSessionEvent(in String name, in Bundle args); - void onVideoStreamChanged(int width, int height, boolean interlaced); - void onAudioStreamChanged(int channelCount); - void onClosedCaptionStreamChanged(boolean hasClosedCaption); void onChannelRetuned(in Uri channelUri); + void onTrackInfoChanged(in List tracks); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index d20ee0e004912..06ee4b57ebec1 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -44,9 +44,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_SET_SURFACE = 2; private static final int DO_SET_VOLUME = 3; private static final int DO_TUNE = 4; - private static final int DO_CREATE_OVERLAY_VIEW = 5; - private static final int DO_RELAYOUT_OVERLAY_VIEW = 6; - private static final int DO_REMOVE_OVERLAY_VIEW = 7; + private static final int DO_SELECT_TRACK = 5; + private static final int DO_UNSELECT_TRACK = 6; + private static final int DO_CREATE_OVERLAY_VIEW = 7; + private static final int DO_RELAYOUT_OVERLAY_VIEW = 8; + private static final int DO_REMOVE_OVERLAY_VIEW = 9; private final HandlerCaller mCaller; @@ -96,6 +98,14 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.tune((Uri) msg.obj); return; } + case DO_SELECT_TRACK: { + mTvInputSessionImpl.selectTrack((TvTrackInfo) msg.obj); + return; + } + case DO_UNSELECT_TRACK: { + mTvInputSessionImpl.unselectTrack((TvTrackInfo) msg.obj); + return; + } case DO_CREATE_OVERLAY_VIEW: { SomeArgs args = (SomeArgs) msg.obj; mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2); @@ -137,6 +147,16 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri)); } + @Override + public void selectTrack(TvTrackInfo track) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SELECT_TRACK, track)); + } + + @Override + public void unselectTrack(TvTrackInfo track) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UNSELECT_TRACK, track)); + } + @Override public void createOverlayView(IBinder windowToken, Rect frame) { mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken, diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 834ce640ed125..521c80951a983 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -35,6 +35,7 @@ import android.view.Surface; import android.view.View; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -88,42 +89,6 @@ public final class TvInputManager { public void onSessionReleased(Session session) { } - /** - * This is called at the beginning of the playback of a channel and later when the format of - * the video stream has been changed. - * - * @param session A {@link TvInputManager.Session} associated with this callback - * @param width The width of the video. - * @param height The height of the video. - * @param interlaced whether the video is interlaced mode or planer mode. - * @hide - */ - public void onVideoStreamChanged(Session session, int width, int height, - boolean interlaced) { - } - - /** - * This is called at the beginning of the playback of a channel and later when the format of - * the audio stream has been changed. - * - * @param session A {@link TvInputManager.Session} associated with this callback - * @param channelCount The number of channels in the audio stream. - * @hide - */ - public void onAudioStreamChanged(Session session, int channelCount) { - } - - /** - * This is called at the beginning of the playback of a channel and later when the closed - * caption stream has been changed. - * - * @param session A {@link TvInputManager.Session} associated with this callback - * @param hasClosedCaption Whether the stream has closed caption or not. - * @hide - */ - public void onClosedCaptionStreamChanged(Session session, boolean hasClosedCaption) { - } - /** * This is called when the channel of this session is changed by the underlying TV input * with out any {@link TvInputManager.Session#tune(Uri)} request. @@ -134,6 +99,15 @@ public final class TvInputManager { public void onChannelRetuned(Session session, Uri channelUri) { } + /** + * This is called when the track information of the session has been changed. + * + * @param session A {@link TvInputManager.Session} associated with this callback + * @param tracks A list which includes track information. + */ + public void onTrackInfoChanged(Session session, List tracks) { + } + /** * This is called when a custom event has been sent from this session. * @@ -176,34 +150,6 @@ public final class TvInputManager { }); } - public void postVideoStreamChanged(final int width, final int height, - final boolean interlaced) { - mHandler.post(new Runnable() { - @Override - public void run() { - mSessionCallback.onVideoStreamChanged(mSession, width, height, interlaced); - } - }); - } - - public void postAudioStreamChanged(final int channelCount) { - mHandler.post(new Runnable() { - @Override - public void run() { - mSessionCallback.onAudioStreamChanged(mSession, channelCount); - } - }); - } - - public void postClosedCaptionStreamChanged(final boolean hasClosedCaption) { - mHandler.post(new Runnable() { - @Override - public void run() { - mSessionCallback.onClosedCaptionStreamChanged(mSession, hasClosedCaption); - } - }); - } - public void postChannelRetuned(final Uri channelUri) { mHandler.post(new Runnable() { @Override @@ -213,6 +159,16 @@ public final class TvInputManager { }); } + public void postTrackInfoChanged(final List tracks) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSession.setTracks(tracks); + mSessionCallback.onTrackInfoChanged(mSession, tracks); + } + }); + } + public void postSessionEvent(final String eventType, final Bundle eventArgs) { mHandler.post(new Runnable() { @Override @@ -300,42 +256,6 @@ public final class TvInputManager { } } - @Override - public void onVideoStreamChanged(int width, int height, boolean interlaced, int seq) { - synchronized (mSessionCallbackRecordMap) { - SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); - if (record == null) { - Log.e(TAG, "Callback not found for seq " + seq); - return; - } - record.postVideoStreamChanged(width, height, interlaced); - } - } - - @Override - public void onAudioStreamChanged(int channelCount, int seq) { - synchronized (mSessionCallbackRecordMap) { - SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); - if (record == null) { - Log.e(TAG, "Callback not found for seq " + seq); - return; - } - record.postAudioStreamChanged(channelCount); - } - } - - @Override - public void onClosedCaptionStreamChanged(boolean hasClosedCaption, int seq) { - synchronized (mSessionCallbackRecordMap) { - SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); - if (record == null) { - Log.e(TAG, "Callback not found for seq " + seq); - return; - } - record.postClosedCaptionStreamChanged(hasClosedCaption); - } - } - @Override public void onChannelRetuned(Uri channelUri, int seq) { synchronized (mSessionCallbackRecordMap) { @@ -348,6 +268,18 @@ public final class TvInputManager { } } + @Override + public void onTrackInfoChanged(List tracks, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postTrackInfoChanged(tracks); + } + } + @Override public void onSessionEvent(String eventType, Bundle eventArgs, int seq) { synchronized (mSessionCallbackRecordMap) { @@ -550,6 +482,7 @@ public final class TvInputManager { private IBinder mToken; private TvInputEventSender mSender; private InputChannel mChannel; + private List mTracks; /** @hide */ private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId, @@ -633,6 +566,7 @@ public final class TvInputManager { Log.w(TAG, "The session has been already released"); return; } + mTracks = null; try { mService.tune(mToken, channelUri, mUserId); } catch (RemoteException e) { @@ -640,6 +574,65 @@ public final class TvInputManager { } } + /** + * Select a track. + * + * @param track the track to be selected. + * @see #getTracks() + */ + public void selectTrack(TvTrackInfo track) { + if (track == null) { + throw new IllegalArgumentException("track cannot be null"); + } + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.selectTrack(mToken, track, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Unselect a track. + * + * @param track the track to be selected. + * @see #getTracks() + */ + public void unselectTrack(TvTrackInfo track) { + if (track == null) { + throw new IllegalArgumentException("track cannot be null"); + } + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.unselectTrack(mToken, track, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns a list which includes track information. May return {@code null} if the + * information is not available. + * @see #selectTrack(TvTrackInfo) + * @see #unselectTrack(TvTrackInfo) + */ + public List getTracks() { + if (mTracks == null) { + return null; + } + return new ArrayList(mTracks); + } + + private void setTracks(List tracks) { + mTracks = tracks; + } + /** * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView} * should be called whenever the layout of its containing view is changed. diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 1e512cd412130..4614bda02d2ee 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -44,6 +44,8 @@ import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; +import java.util.List; + /** * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which * provides pass-through video or broadcast TV programs. @@ -219,72 +221,6 @@ public abstract class TvInputService extends Service { }); } - /** - * Sends the change on the format of the video stream. This is expected to be called at the - * beginning of the playback and later when the format has been changed. - * - * @param width The width of the video. - * @param height The height of the video. - * @param interlaced Whether the video is interlaced mode or planer mode. - * @hide - */ - public void dispatchVideoStreamChanged(final int width, final int height, - final boolean interlaced) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - if (DEBUG) Log.d(TAG, "dispatchVideoSizeChanged"); - mSessionCallback.onVideoStreamChanged(width, height, interlaced); - } catch (RemoteException e) { - Log.w(TAG, "error in dispatchVideoSizeChanged"); - } - } - }); - } - - /** - * Sends the change on the format of the audio stream. This is expected to be called at the - * beginning of the playback and later when the format has been changed. - * - * @param channelNumber The number of channels in the audio stream. - * @hide - */ - public void dispatchAudioStreamChanged(final int channelNumber) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - if (DEBUG) Log.d(TAG, "dispatchAudioStreamChanged"); - mSessionCallback.onAudioStreamChanged(channelNumber); - } catch (RemoteException e) { - Log.w(TAG, "error in dispatchAudioStreamChanged"); - } - } - }); - } - - /** - * Sends the change on the closed caption stream. This is expected to be called at the - * beginning of the playback and later when the stream has been changed. - * - * @param hasClosedCaption Whether the stream has closed caption or not. - * @hide - */ - public void dispatchClosedCaptionStreamChanged(final boolean hasClosedCaption) { - mHandler.post(new Runnable() { - @Override - public void run() { - try { - if (DEBUG) Log.d(TAG, "dispatchClosedCaptionStreamChanged"); - mSessionCallback.onClosedCaptionStreamChanged(hasClosedCaption); - } catch (RemoteException e) { - Log.w(TAG, "error in dispatchClosedCaptionStreamChanged"); - } - } - }); - } - /** * Notifies the channel of the session is retuned by TV input. * @@ -304,6 +240,30 @@ public abstract class TvInputService extends Service { }); } + /** + * Sends the change on the track information. This is expected to be called whenever a + * track is added/removed and the metadata of a track is modified. + * + * @param tracks A list which includes track information. + */ + public void dispatchTrackInfoChanged(final List tracks) { + if (!TvTrackInfo.checkSanity(tracks)) { + throw new IllegalArgumentException( + "Two or more selected tracks for a track type."); + } + mHandler.post(new Runnable() { + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "dispatchTrackInfoChanged"); + mSessionCallback.onTrackInfoChanged(tracks); + } catch (RemoteException e) { + Log.w(TAG, "error in dispatchTrackInfoChanged"); + } + } + }); + } + /** * Called when the session is released. */ @@ -334,6 +294,38 @@ public abstract class TvInputService extends Service { */ public abstract boolean onTune(Uri channelUri); + /** + * Selects a given track. + *

+ * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the + * track selected previously should be unselected in the implementation of this method. + * Also, if the select operation was successful, the implementation should call + * {@link #dispatchTrackInfoChanged(List)} to report the updated track information. + *

+ * @param track The track to be selected. + * @return {@code true} if the select operation was successful, {@code false} otherwise. + * @see #dispatchTrackInfoChanged(TvTrackInfo[]) + * @see TvTrackInfo#KEY_IS_SELECTED + */ + public boolean onSelectTrack(TvTrackInfo track) { + return false; + } + + /** + * Unselects a given track. + *

+ * If the unselect operation was successful, the implementation should call + * {@link #dispatchTrackInfoChanged(List)} to report the updated track information. + *

+ * @param track The track to be unselected. + * @return {@code true} if the unselect operation was successful, {@code false} otherwise. + * @see #dispatchTrackInfoChanged(TvTrackInfo[]) + * @see TvTrackInfo#KEY_IS_SELECTED + */ + public boolean onUnselectTrack(TvTrackInfo track) { + return false; + } + /** * Called when an application requests to create an overlay view. Each session * implementation can override this method and return its own view. @@ -499,6 +491,22 @@ public abstract class TvInputService extends Service { // TODO: Handle failure. } + /** + * Calls {@link #onSelectTrack}. + */ + void selectTrack(TvTrackInfo track) { + onSelectTrack(track); + // TODO: Handle failure. + } + + /** + * Calls {@link #onUnselectTrack}. + */ + void unselectTrack(TvTrackInfo track) { + onUnselectTrack(track); + // TODO: Handle failure. + } + /** * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach * to the overlay window. diff --git a/media/java/android/media/tv/TvTrackInfo.aidl b/media/java/android/media/tv/TvTrackInfo.aidl new file mode 100644 index 0000000000000..63b6b2a2348d8 --- /dev/null +++ b/media/java/android/media/tv/TvTrackInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +parcelable TvTrackInfo; \ No newline at end of file diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java new file mode 100644 index 0000000000000..de4f4b73afadd --- /dev/null +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.media.MediaFormat; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Encapsulates the format of tracks played in {@link TvInputService}. + */ +public final class TvTrackInfo implements Parcelable { + /** + * A key describing the type of this track. The associated value is an integer and it should be + * one of {@link #VALUE_TYPE_AUDIO}, {@link #VALUE_TYPE_VIDEO}, and {@link #VALUE_TYPE_SUBTITLE}. + *

+ * This is a required key. + *

+ */ + public static final String KEY_TYPE = "type"; + + /** + * A key describing the language of the track, using either ISO 639-1 or 639-2/T codes. + * If the language is unknown or could not be determined, the corresponding value will be "und". + * The associated value is a string. + *

+ * This is a required key. + *

+ */ + public static final String KEY_LANGUAGE = MediaFormat.KEY_LANGUAGE; + + /** + * A key describing whether this track is selected for the playback. + * The associated value is a boolean. + *

+ * This is a required key. + *

+ */ + public static final String KEY_IS_SELECTED = "is-selected"; + + /** + * A key describing the sample rate of an audio track. + * The associated value is an integer. + */ + public static final String KEY_SAMPLE_RATE = MediaFormat.KEY_SAMPLE_RATE; + + /** + * A key describing the number of channels in an audio track. + * The associated value is an integer. + */ + public static final String KEY_CHANNEL_COUNT = MediaFormat.KEY_CHANNEL_COUNT; + + /** + * A key describing the width of the content in a video track. + * The associated value is an integer. + */ + public static final String KEY_WIDTH = MediaFormat.KEY_WIDTH; + + /** + * A key describing the height of the content in a video track. + * The associated value is an integer. + */ + public static final String KEY_HEIGHT = MediaFormat.KEY_HEIGHT; + + /** + * A key describing a tag associated with this track. Expected to be used as an identifier with + * in a session. The associated value is a string. + */ + public static final String KEY_TAG = "tag"; + + /** + * The type value for audio track. + */ + public static final int VALUE_TYPE_AUDIO = 0; + + /** + * The type value for video track. + */ + public static final int VALUE_TYPE_VIDEO = 1; + + /** + * The type value for subtitle track. + */ + public static final int VALUE_TYPE_SUBTITLE = 2; + + private final Bundle mBundle; + + private TvTrackInfo(Bundle bundle) { + mBundle = new Bundle(bundle); + } + + private TvTrackInfo(Parcel in) { + mBundle = in.readBundle(); + } + + /** + * Checks if there is only one or zero selected track per track type. + * + * @param tracks a list including tracks which will be checked. + * @return true if there is only one or zero selected track per track type, false otherwise + * @hide + */ + public static boolean checkSanity(List tracks) { + int selectedAudioTracks = 0; + int selectedVideoTracks = 0; + int selectedSubtitleTracks = 0; + for (TvTrackInfo track : tracks) { + if (track.getBoolean(KEY_IS_SELECTED)) { + int type = track.getInt(KEY_TYPE); + if (type == VALUE_TYPE_AUDIO) { + selectedAudioTracks++; + } else if (type == VALUE_TYPE_VIDEO) { + selectedVideoTracks++; + } else if (type == VALUE_TYPE_SUBTITLE) { + selectedSubtitleTracks++; + } + } + } + if (selectedAudioTracks > 1 || selectedVideoTracks > 1 || selectedSubtitleTracks > 1) { + return false; + } + return true; + } + + /** + * Returns true if the given key is contained in the metadata + * + * @param key A String key + * @return true If the key exists in this metadata, false otherwise + */ + public boolean containsKey(String key) { + return mBundle.containsKey(key); + } + + /** + * Returns the value associated with the given key, or null if no mapping of + * the desired type exists for the given key or a null value is explicitly + * associated with the key. + * + * @param key The key the value is stored under + * @return A String value, or null + */ + public String getString(String key) { + return mBundle.getString(key); + } + + /** + * Returns the value associated with the given key, or 0L if no integer exists + * for the given key. + * + * @param key The key the value is stored under + * @return An integer value + */ + public int getInt(String key) { + return mBundle.getInt(key, 0); + } + + /** + * Returns the value associated with the given key, or false if no integer exists + * for the given key. + * + * @param key The key the value is stored under + * @return A boolean value + */ + public boolean getBoolean(String key) { + return mBundle.getBoolean(key, false); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBundle(mBundle); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public TvTrackInfo createFromParcel(Parcel in) { + return new TvTrackInfo(in); + } + + @Override + public TvTrackInfo[] newArray(int size) { + return new TvTrackInfo[size]; + } + }; + + /** + * A builder class for creating {@link TvTrackInfo} objects. + */ + public static final class Builder { + private final Bundle mBundle; + + /** + * Create a {@link Builder}. Any field that should be included in the + * {@link TvTrackInfo} must be added. + * + * @param type The type of the track. + * @param language The language of the track, using either ISO 639-1 or 639-2/T codes. + * "und" if the language is unknown. + * @param isSelected Whether this track is selected for the playback or not. + */ + public Builder(int type, String language, boolean isSelected) { + if (type != VALUE_TYPE_AUDIO + && type != VALUE_TYPE_VIDEO + && type != VALUE_TYPE_SUBTITLE) { + throw new IllegalArgumentException("Unknown type: " + type); + } + mBundle = new Bundle(); + putInt(KEY_TYPE, type); + putString(KEY_LANGUAGE, language); + putBoolean(KEY_IS_SELECTED, isSelected); + } + + /** + * Create a Builder using a {@link TvTrackInfo} instance to set the + * initial values. All fields in the source metadata will be included in + * the new metadata. Fields can be overwritten by adding the same key. + * + * @param source The source {@link TvTrackInfo} instance + */ + public Builder(TvTrackInfo source) { + mBundle = new Bundle(source.mBundle); + } + + /** + * Put a String value into the track. + * + * @param key The key for referencing this value + * @param value The String value to store + * @return The Builder to allow chaining + */ + public Builder putString(String key, String value) { + mBundle.putString(key, value); + return this; + } + + /** + * Put an integer value into the track. + * + * @param key The key for referencing this value + * @param value The integer value to store + * @return The Builder to allow chaining + */ + public Builder putInt(String key, int value) { + mBundle.putInt(key, value); + return this; + } + + /** + * Put a boolean value into the track. + * + * @param key The key for referencing this value + * @param value The boolean value to store + * @return The Builder to allow chaining + */ + public Builder putBoolean(String key, boolean value) { + mBundle.putBoolean(key, value); + return this; + } + + /** + * Creates a {@link TvTrackInfo} instance with the specified fields. + * + * @return The new {@link TvTrackInfo} instance + */ + public TvTrackInfo build() { + return new TvTrackInfo(mBundle); + } + } +} \ No newline at end of file diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 664a215f12803..10f0c6be3c78c 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -18,6 +18,7 @@ package android.media.tv; import android.content.Context; import android.graphics.Rect; +import android.media.MediaPlayer.TrackInfo; import android.media.tv.TvInputManager.Session; import android.media.tv.TvInputManager.Session.FinishedInputEventCallback; import android.media.tv.TvInputManager.SessionCallback; @@ -37,6 +38,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; +import java.util.List; + /** * View playing TV */ @@ -57,6 +60,8 @@ public class TvView extends ViewGroup { */ public static final int ERROR_TV_INPUT_DISCONNECTED = 1; + private static final int VIDEO_SIZE_VALUE_UNKNOWN = 0; + private final Handler mHandler = new Handler(); private TvInputManager.Session mSession; private final SurfaceView mSurfaceView; @@ -69,6 +74,8 @@ public class TvView extends ViewGroup { private OnUnhandledInputEventListener mOnUnhandledInputEventListener; private boolean mHasStreamVolume; private float mStreamVolume; + private int mVideoWidth = VIDEO_SIZE_VALUE_UNKNOWN; + private int mVideoHeight = VIDEO_SIZE_VALUE_UNKNOWN; private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { @Override @@ -208,6 +215,44 @@ public class TvView extends ViewGroup { } } + /** + * Select a track. + *

+ * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the track + * selected in previous will be unselected. + *

+ * @param track the track to be selected. + * @see #getTracks() + */ + public void selectTrack(TvTrackInfo track) { + if (mSession != null) { + mSession.selectTrack(track); + } + } + + /** + * Unselect a track. + * + * @param track the track to be unselected. + * @see #getTracks() + */ + public void unselectTrack(TvTrackInfo track) { + if (mSession != null) { + mSession.unselectTrack(track); + } + } + + /** + * Returns a list which includes of track information. May return {@code null} if the + * information is not available. + */ + public List getTracks() { + if (mSession == null) { + return null; + } + return mSession.getTracks(); + } + /** * Dispatches an unhandled input event to the next receiver. *

@@ -401,6 +446,23 @@ public class TvView extends ViewGroup { location[0] + getWidth(), location[1] + getHeight()); } + private void updateVideoSize(List tracks) { + for (TvTrackInfo track : tracks) { + if (track.getBoolean(TvTrackInfo.KEY_IS_SELECTED) + && track.getInt(TvTrackInfo.KEY_TYPE) == TvTrackInfo.VALUE_TYPE_VIDEO) { + int width = track.getInt(TvTrackInfo.KEY_WIDTH); + int height = track.getInt(TvTrackInfo.KEY_HEIGHT); + if (width != mVideoWidth || height != mVideoHeight) { + mVideoWidth = width; + mVideoHeight = height; + if (mListener != null) { + mListener.onVideoSizeChanged(mSessionCallback.mInputId, width, height); + } + } + } + } + } + /** * Interface used to receive various status updates on the {@link TvView}. */ @@ -418,41 +480,13 @@ public class TvView extends ViewGroup { /** * This is invoked when the view is tuned to a specific channel and starts decoding video - * stream from there. It is also called later when the video format is changed. + * stream from there. It is also called later when the video size is changed. * * @param inputId The ID of the TV input bound to this view. * @param width The width of the video. * @param height The height of the video. - * @param interlaced {@code true} if the video is interlaced, {@code false} if the video is - * progressive. - * @hide */ - public void onVideoStreamChanged(String inputId, int width, int height, - boolean interlaced) { - } - - /** - * This is invoked when the view is tuned to a specific channel and starts decoding audio - * stream from there. It is also called later when the audio format is changed. - * - * @param inputId The ID of the TV input bound to this view. - * @param channelCount The number of channels in the audio stream. - * @hide - */ - public void onAudioStreamChanged(String inputId, int channelCount) { - } - - /** - * This is invoked when the view is tuned to a specific channel and starts decoding data - * stream that includes subtitle information from the channel. It is also called later when - * the information disappears or appears. - * - * @param inputId The ID of the TV input bound to this view. - * @param hasClosedCaption {@code true} if the stream contains closed caption, {@code false} - * otherwise. - * @hide - */ - public void onClosedCaptionStreamChanged(String inputId, boolean hasClosedCaption) { + public void onVideoSizeChanged(String inputId, int width, int height) { } /** @@ -465,6 +499,15 @@ public class TvView extends ViewGroup { public void onChannelRetuned(String inputId, Uri channelUri) { } + /** + * This is called when the track information has been changed. + * + * @param inputId The ID of the TV input bound to this view. + * @param tracks A list which includes track information. + */ + public void onTrackInfoChanged(String inputId, List tracks) { + } + /** * This is invoked when a custom event from the bound TV input is sent to this view. * @@ -533,44 +576,15 @@ public class TvView extends ViewGroup { @Override public void onSessionReleased(Session session) { + if (this == mSessionCallback) { + mSessionCallback = null; + } mSession = null; - mSessionCallback = null; if (mListener != null) { mListener.onError(mInputId, ERROR_TV_INPUT_DISCONNECTED); } } - @Override - public void onVideoStreamChanged(Session session, int width, int height, - boolean interlaced) { - if (DEBUG) { - Log.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")"); - } - if (mListener != null) { - mListener.onVideoStreamChanged(mInputId, width, height, interlaced); - } - } - - @Override - public void onAudioStreamChanged(Session session, int channelCount) { - if (DEBUG) { - Log.d(TAG, "onAudioStreamChanged(" + channelCount + ")"); - } - if (mListener != null) { - mListener.onAudioStreamChanged(mInputId, channelCount); - } - } - - @Override - public void onClosedCaptionStreamChanged(Session session, boolean hasClosedCaption) { - if (DEBUG) { - Log.d(TAG, "onClosedCaptionStreamChanged(" + hasClosedCaption + ")"); - } - if (mListener != null) { - mListener.onClosedCaptionStreamChanged(mInputId, hasClosedCaption); - } - } - @Override public void onChannelRetuned(Session session, Uri channelUri) { if (DEBUG) { @@ -581,9 +595,26 @@ public class TvView extends ViewGroup { } } + @Override + public void onTrackInfoChanged(Session session, List tracks) { + if (this != mSessionCallback) { + return; + } + if (DEBUG) { + Log.d(TAG, "onTrackInfoChanged()"); + } + updateVideoSize(tracks); + if (mListener != null) { + mListener.onTrackInfoChanged(mInputId, tracks); + } + } + @Override public void onSessionEvent(TvInputManager.Session session, String eventType, Bundle eventArgs) { + if (this != mSessionCallback) { + return; + } if (mListener != null) { mListener.onEvent(mInputId, eventType, eventArgs); } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index bf767e2376293..6be4d4f3fd2e8 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -46,6 +46,7 @@ import android.media.tv.TvContract; import android.media.tv.TvInputHardwareInfo; import android.media.tv.TvInputInfo; import android.media.tv.TvInputService; +import android.media.tv.TvTrackInfo; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -431,59 +432,6 @@ public final class TvInputManagerService extends SystemService { } } - @Override - public void onVideoStreamChanged(int width, int height, boolean interlaced) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onVideoStreamChanged(" + width + ", " + height + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onVideoStreamChanged(width, height, interlaced, - sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onVideoStreamChanged"); - } - } - } - - @Override - public void onAudioStreamChanged(int channelCount) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onAudioStreamChanged(" + channelCount + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onAudioStreamChanged(channelCount, sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onAudioStreamChanged"); - } - } - } - - @Override - public void onClosedCaptionStreamChanged(boolean hasClosedCaption) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "onClosedCaptionStreamChanged(" + hasClosedCaption + ")"); - } - if (sessionState.mSession == null || sessionState.mClient == null) { - return; - } - try { - sessionState.mClient.onClosedCaptionStreamChanged(hasClosedCaption, - sessionState.mSeq); - } catch (RemoteException e) { - Slog.e(TAG, "error in onClosedCaptionStreamChanged"); - } - } - } - @Override public void onChannelRetuned(Uri channelUri) { synchronized (mLock) { @@ -505,6 +453,24 @@ public final class TvInputManagerService extends SystemService { } } + @Override + public void onTrackInfoChanged(List tracks) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onTrackInfoChanged(" + tracks + ")"); + } + if (sessionState.mSession == null || sessionState.mClient == null) { + return; + } + try { + sessionState.mClient.onTrackInfoChanged(tracks, + sessionState.mSeq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onTrackInfoChanged"); + } + } + } + @Override public void onSessionEvent(String eventType, Bundle eventArgs) { synchronized (mLock) { @@ -894,6 +860,46 @@ public final class TvInputManagerService extends SystemService { } } + @Override + public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "selectTrack"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack( + track); + } catch (RemoteException e) { + Slog.e(TAG, "error in selectTrack", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void unselectTrack(IBinder sessionToken, TvTrackInfo track, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "unselectTrack"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).unselectTrack( + track); + } catch (RemoteException e) { + Slog.e(TAG, "error in unselectTrack", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public void createOverlayView(IBinder sessionToken, IBinder windowToken, Rect frame, int userId) {