Add Audio Presentation APIs to TIF

Add APIs to obtain list of audio presentation from an NGA service and to
enable the user to select a given one.

Bug: 230776612

Change-Id: Id25ef9f27fadc1dffd846698348ddf37d6a0112e
(cherry picked from commit 19a92e4b0107ab1dc63244a3e8b3cfecdcc80108)
This commit is contained in:
Gael Lassure
2022-01-27 17:27:29 +11:00
committed by Yixiao Luo
parent 7b5efcae5a
commit 2f8382157b
12 changed files with 554 additions and 17 deletions

View File

@@ -21369,7 +21369,8 @@ package android.media {
field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
}
public final class AudioPresentation {
public final class AudioPresentation implements android.os.Parcelable {
method public int describeContents();
method public java.util.Map<java.util.Locale,java.lang.String> getLabels();
method public java.util.Locale getLocale();
method public int getMasteringIndication();
@@ -21378,6 +21379,7 @@ package android.media {
method public boolean hasAudioDescription();
method public boolean hasDialogueEnhancement();
method public boolean hasSpokenSubtitles();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int CONTENT_COMMENTARY = 5; // 0x5
field public static final int CONTENT_DIALOG = 4; // 0x4
field public static final int CONTENT_EMERGENCY = 6; // 0x6
@@ -21387,11 +21389,14 @@ package android.media {
field public static final int CONTENT_UNKNOWN = -1; // 0xffffffff
field public static final int CONTENT_VISUALLY_IMPAIRED = 2; // 0x2
field public static final int CONTENT_VOICEOVER = 7; // 0x7
field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioPresentation> CREATOR;
field public static final int MASTERED_FOR_3D = 3; // 0x3
field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4
field public static final int MASTERED_FOR_STEREO = 1; // 0x1
field public static final int MASTERED_FOR_SURROUND = 2; // 0x2
field public static final int MASTERING_NOT_INDICATED = 0; // 0x0
field public static final int PRESENTATION_ID_UNKNOWN = -1; // 0xffffffff
field public static final int PROGRAM_ID_UNKNOWN = -1; // 0xffffffff
}
public static final class AudioPresentation.Builder {
@@ -27062,6 +27067,8 @@ package android.media.tv {
method public void layoutSurface(int, int, int, int);
method public void notifyAdResponse(@NonNull android.media.tv.AdResponse);
method public void notifyAitInfoUpdated(@NonNull android.media.tv.AitInfo);
method public void notifyAudioPresentationChanged(@NonNull java.util.List<android.media.AudioPresentation>);
method public void notifyAudioPresentationSelected(int, int);
method public void notifyBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
method public void notifyChannelRetuned(android.net.Uri);
method public void notifyContentAllowed();
@@ -27085,6 +27092,7 @@ package android.media.tv {
method public void onRemoveBroadcastInfo(int);
method public void onRequestAd(@NonNull android.media.tv.AdRequest);
method public void onRequestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
method public boolean onSelectAudioPresentation(int, int);
method public boolean onSelectTrack(int, @Nullable String);
method public abstract void onSetCaptionEnabled(boolean);
method public void onSetInteractiveAppNotificationEnabled(boolean);
@@ -27181,10 +27189,12 @@ package android.media.tv {
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 @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();
method public String getSelectedTrack(int);
method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int);
method public boolean onUnhandledInputEvent(android.view.InputEvent);
method public void reset();
method public void selectAudioPresentation(int, int);
method public void selectTrack(int, String);
method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle);
method public void setCallback(@Nullable android.media.tv.TvView.TvInputCallback);
@@ -27217,6 +27227,8 @@ package android.media.tv {
public abstract static class TvView.TvInputCallback {
ctor public TvView.TvInputCallback();
method public void onAitInfoUpdated(@NonNull String, @NonNull android.media.tv.AitInfo);
method public void onAudioPresentationSelected(@NonNull String, int, int);
method public void onAudioPresentationsChanged(@NonNull String, @NonNull java.util.List<android.media.AudioPresentation>);
method public void onChannelRetuned(String, android.net.Uri);
method public void onConnectionFailed(String);
method public void onContentAllowed(String);

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2022 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;
parcelable AudioPresentation;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* Copyright (C) 2022 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.
@@ -19,6 +19,9 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.icu.util.ULocale;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -49,7 +52,7 @@ import java.util.Objects;
* Applications that parse media streams and extract presentation information on their own
* can create instances of AudioPresentation by using {@link AudioPresentation.Builder} class.
*/
public final class AudioPresentation {
public final class AudioPresentation implements Parcelable {
private final int mPresentationId;
private final int mProgramId;
private final ULocale mLanguage;
@@ -126,7 +129,7 @@ public final class AudioPresentation {
private final boolean mAudioDescriptionAvailable;
private final boolean mSpokenSubtitlesAvailable;
private final boolean mDialogueEnhancementAvailable;
private final Map<ULocale, CharSequence> mLabels;
private final HashMap<ULocale, String> mLabels;
/**
* No preferred reproduction channel layout.
@@ -160,9 +163,14 @@ public final class AudioPresentation {
public static final int MASTERED_FOR_HEADPHONE = 4;
/**
* This ID is reserved. No items can be explicitly assigned this ID.
* Unknown audio presentation ID, this indicates audio presentation ID is not selected.
*/
private static final int UNKNOWN_ID = -1;
public static final int PRESENTATION_ID_UNKNOWN = -1;
/**
* Unknown audio program ID, this indicates audio program ID is not selected.
*/
public static final int PROGRAM_ID_UNKNOWN = -1;
/**
* This allows an application developer to construct an AudioPresentation object with all the
@@ -191,7 +199,7 @@ public final class AudioPresentation {
boolean audioDescriptionAvailable,
boolean spokenSubtitlesAvailable,
boolean dialogueEnhancementAvailable,
@NonNull Map<ULocale, CharSequence> labels) {
@NonNull Map<ULocale, String> labels) {
mPresentationId = presentationId;
mProgramId = programId;
mLanguage = language;
@@ -199,7 +207,18 @@ public final class AudioPresentation {
mAudioDescriptionAvailable = audioDescriptionAvailable;
mSpokenSubtitlesAvailable = spokenSubtitlesAvailable;
mDialogueEnhancementAvailable = dialogueEnhancementAvailable;
mLabels = new HashMap<ULocale, CharSequence>(labels);
mLabels = new HashMap<ULocale, String>(labels);
}
private AudioPresentation(@NonNull Parcel in) {
mPresentationId = in.readInt();
mProgramId = in.readInt();
mLanguage = in.readSerializable(ULocale.class.getClassLoader(), ULocale.class);
mMasteringIndication = in.readInt();
mAudioDescriptionAvailable = in.readBoolean();
mSpokenSubtitlesAvailable = in.readBoolean();
mDialogueEnhancementAvailable = in.readBoolean();
mLabels = in.readSerializable(HashMap.class.getClassLoader(), HashMap.class);
}
/**
@@ -225,13 +244,13 @@ public final class AudioPresentation {
*/
public Map<Locale, String> getLabels() {
Map<Locale, String> localeLabels = new HashMap<Locale, String>(mLabels.size());
for (Map.Entry<ULocale, CharSequence> entry : mLabels.entrySet()) {
localeLabels.put(entry.getKey().toLocale(), entry.getValue().toString());
for (Map.Entry<ULocale, String> entry : mLabels.entrySet()) {
localeLabels.put(entry.getKey().toLocale(), entry.getValue());
}
return localeLabels;
}
private Map<ULocale, CharSequence> getULabels() {
private Map<ULocale, String> getULabels() {
return mLabels;
}
@@ -335,13 +354,13 @@ public final class AudioPresentation {
*/
public static final class Builder {
private final int mPresentationId;
private int mProgramId = UNKNOWN_ID;
private int mProgramId = PROGRAM_ID_UNKNOWN;
private ULocale mLanguage = new ULocale("");
private int mMasteringIndication = MASTERING_NOT_INDICATED;
private boolean mAudioDescriptionAvailable = false;
private boolean mSpokenSubtitlesAvailable = false;
private boolean mDialogueEnhancementAvailable = false;
private Map<ULocale, CharSequence> mLabels = new HashMap<ULocale, CharSequence>();
private HashMap<ULocale, String> mLabels = new HashMap<ULocale, String>();
/**
* Create a {@link Builder}. Any field that should be included in the
@@ -402,7 +421,10 @@ public final class AudioPresentation {
* @param labels Text label indexed by its locale corresponding to the language code.
*/
public @NonNull Builder setLabels(@NonNull Map<ULocale, CharSequence> labels) {
mLabels = new HashMap<ULocale, CharSequence>(labels);
mLabels.clear();
for (Map.Entry<ULocale, CharSequence> entry : labels.entrySet()) {
mLabels.put(entry.getKey(), entry.getValue().toString());
}
return this;
}
@@ -448,4 +470,35 @@ public final class AudioPresentation {
mDialogueEnhancementAvailable, mLabels);
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(getPresentationId());
dest.writeInt(getProgramId());
dest.writeSerializable(getULocale());
dest.writeInt(getMasteringIndication());
dest.writeBoolean(hasAudioDescription());
dest.writeBoolean(hasSpokenSubtitles());
dest.writeBoolean(hasDialogueEnhancement());
dest.writeSerializable(mLabels);
}
@NonNull
public static final Parcelable.Creator<AudioPresentation> CREATOR =
new Parcelable.Creator<AudioPresentation>() {
@Override
public AudioPresentation createFromParcel(@NonNull Parcel in) {
return new AudioPresentation(in);
}
@Override
public AudioPresentation[] newArray(int size) {
return new AudioPresentation[size];
}
};
}

View File

@@ -17,6 +17,7 @@
package android.media.tv;
import android.content.ComponentName;
import android.media.AudioPresentation;
import android.media.tv.AdBuffer;
import android.media.tv.AdResponse;
import android.media.tv.AitInfo;
@@ -37,6 +38,8 @@ oneway interface ITvInputClient {
void onSessionReleased(int seq);
void onSessionEvent(in String name, in Bundle args, int seq);
void onChannelRetuned(in Uri channelUri, int seq);
void onAudioPresentationsChanged(in List<AudioPresentation> AudioPresentations, int seq);
void onAudioPresentationSelected(int presentationId, int programId, int seq);
void onTracksChanged(in List<TvTrackInfo> tracks, int seq);
void onTrackSelected(int type, in String trackId, int seq);
void onVideoAvailable(int seq);

View File

@@ -19,6 +19,7 @@ package android.media.tv;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Rect;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.AdBuffer;
import android.media.tv.AdRequest;
@@ -77,6 +78,8 @@ interface ITvInputManager {
void tune(in IBinder sessionToken, in Uri channelUri, in Bundle params, int userId);
void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
void selectTrack(in IBinder sessionToken, int type, in String trackId, int userId);
void selectAudioPresentation(in IBinder sessionToken, int presentationId, int programId,
int userId);
void setInteractiveAppNotificationEnabled(in IBinder sessionToken, boolean enabled, int userId);

View File

@@ -17,6 +17,7 @@
package android.media.tv;
import android.graphics.Rect;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.AdBuffer;
import android.media.tv.AdRequest;
@@ -41,6 +42,7 @@ oneway interface ITvInputSession {
void setVolume(float volume);
void tune(in Uri channelUri, in Bundle params);
void setCaptionEnabled(boolean enabled);
void selectAudioPresentation(int presentationId, int programId);
void selectTrack(int type, in String trackId);
void setInteractiveAppNotificationEnabled(boolean enable);

View File

@@ -16,6 +16,7 @@
package android.media.tv;
import android.media.AudioPresentation;
import android.media.tv.AdBuffer;
import android.media.tv.AdResponse;
import android.media.tv.AitInfo;
@@ -34,6 +35,8 @@ oneway interface ITvInputSessionCallback {
void onSessionCreated(ITvInputSession session, in IBinder hardwareSessionToken);
void onSessionEvent(in String name, in Bundle args);
void onChannelRetuned(in Uri channelUri);
void onAudioPresentationsChanged(in List<AudioPresentation> tvAudioPresentations);
void onAudioPresentationSelected(int presentationId, int programId);
void onTracksChanged(in List<TvTrackInfo> tracks);
void onTrackSelected(int type, in String trackId);
void onVideoAvailable();

View File

@@ -75,6 +75,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 26;
private static final int DO_REQUEST_AD = 27;
private static final int DO_NOTIFY_AD_BUFFER = 28;
private static final int DO_SELECT_AUDIO_PRESENTATION = 29;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -240,6 +241,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mTvInputRecordingSessionImpl.resumeRecording((Bundle) msg.obj);
break;
}
case DO_SELECT_AUDIO_PRESENTATION: {
SomeArgs args = (SomeArgs) msg.obj;
mTvInputSessionImpl.selectAudioPresentation(args.argi1, args.argi2);
args.recycle();
break;
}
case DO_REQUEST_BROADCAST_INFO: {
mTvInputSessionImpl.requestBroadcastInfo((BroadcastInfoRequest) msg.obj);
break;
@@ -275,8 +282,8 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
+ "Consider handling the tune request in a separate thread.");
}
if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
throw new RuntimeException("Too much time to handle a request. (type=" + msg.what +
", " + durationMs + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
throw new RuntimeException("Too much time to handle a request. (type=" + msg.what
+ ", " + durationMs + "ms > " + EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS + "ms).");
}
}
}
@@ -322,6 +329,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_CAPTION_ENABLED, enabled));
}
@Override
public void selectAudioPresentation(int presentationId, int programId) {
mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_SELECT_AUDIO_PRESENTATION,
presentationId, programId));
}
@Override
public void selectTrack(int type, String trackId) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_SELECT_TRACK, type, trackId));
@@ -436,7 +449,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
}
private final class TvInputEventReceiver extends InputEventReceiver {
public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}

View File

@@ -31,6 +31,7 @@ import android.content.Intent;
import android.graphics.Rect;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat.Encoding;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.interactive.TvInteractiveAppManager;
import android.net.Uri;
@@ -549,6 +550,27 @@ public final class TvInputManager {
public void onChannelRetuned(Session session, Uri channelUri) {
}
/**
* This is called when the audio presentation information of the session has been changed.
*
* @param session A {@link TvInputManager.Session} associated with this callback.
* @param audioPresentations An updated list of selectable audio presentations.
*/
public void onAudioPresentationsChanged(Session session,
List<AudioPresentation> audioPresentations) {
}
/**
* This is called when an audio presentation is selected.
*
* @param session A {@link TvInputManager.Session} associated with this callback.
* @param presentationId The ID of the selected audio presentation.
* @param programId The ID of the program providing the selected audio presentation.
*/
public void onAudioPresentationSelected(Session session, int presentationId,
int programId) {
}
/**
* This is called when the track information of the session has been changed.
*
@@ -777,6 +799,25 @@ public final class TvInputManager {
});
}
void postAudioPresentationsChanged(final List<AudioPresentation> audioPresentations) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onAudioPresentationsChanged(mSession, audioPresentations);
}
});
}
void postAudioPresentationSelected(final int presentationId, final int programId) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onAudioPresentationSelected(mSession, presentationId,
programId);
}
});
}
void postTracksChanged(final List<TvTrackInfo> tracks) {
mHandler.post(new Runnable() {
@Override
@@ -1234,6 +1275,36 @@ public final class TvInputManager {
record.postChannelRetuned(channelUri);
}
}
@Override
public void onAudioPresentationsChanged(List<AudioPresentation> audioPresentations,
int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
if (record.mSession.updateAudioPresentations(audioPresentations)) {
record.postAudioPresentationsChanged(audioPresentations);
}
}
}
@Override
public void onAudioPresentationSelected(int presentationId, int programId, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
if (record.mSession.updateAudioPresentationSelection(presentationId,
programId)) {
record.postAudioPresentationSelected(presentationId, programId);
}
}
}
@Override
public void onTracksChanged(List<TvTrackInfo> tracks, int seq) {
@@ -2399,12 +2470,18 @@ public final class TvInputManager {
private final Object mMetadataLock = new Object();
// @GuardedBy("mMetadataLock")
private final List<AudioPresentation> mAudioPresentations = new ArrayList<>();
// @GuardedBy("mMetadataLock")
private final List<TvTrackInfo> mAudioTracks = new ArrayList<>();
// @GuardedBy("mMetadataLock")
private final List<TvTrackInfo> mVideoTracks = new ArrayList<>();
// @GuardedBy("mMetadataLock")
private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<>();
// @GuardedBy("mMetadataLock")
private int mSelectedAudioProgramId = AudioPresentation.PROGRAM_ID_UNKNOWN;
// @GuardedBy("mMetadataLock")
private int mSelectedAudioPresentationId = AudioPresentation.PRESENTATION_ID_UNKNOWN;
// @GuardedBy("mMetadataLock")
private String mSelectedAudioTrackId;
// @GuardedBy("mMetadataLock")
private String mSelectedVideoTrackId;
@@ -2552,9 +2629,12 @@ public final class TvInputManager {
return;
}
synchronized (mMetadataLock) {
mAudioPresentations.clear();
mAudioTracks.clear();
mVideoTracks.clear();
mSubtitleTracks.clear();
mSelectedAudioProgramId = AudioPresentation.PROGRAM_ID_UNKNOWN;
mSelectedAudioPresentationId = AudioPresentation.PRESENTATION_ID_UNKNOWN;
mSelectedAudioTrackId = null;
mSelectedVideoTrackId = null;
mSelectedSubtitleTrackId = null;
@@ -2585,6 +2665,119 @@ public final class TvInputManager {
}
}
/**
* Selects an audio presentation
*
* @param presentationId The ID of the audio presentation to select.
* @param programId The ID of the program offering the selected audio presentation.
* @see #getAudioPresentations
*/
public void selectAudioPresentation(int presentationId, int programId) {
synchronized (mMetadataLock) {
if (presentationId != AudioPresentation.PRESENTATION_ID_UNKNOWN
&& !containsAudioPresentation(mAudioPresentations, presentationId)) {
Log.w(TAG, "Invalid audio presentation id: " + presentationId);
return;
}
}
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
}
try {
mService.selectAudioPresentation(mToken, presentationId, programId, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private boolean containsAudioPresentation(List<AudioPresentation> audioPresentations,
int presentationId) {
synchronized (mMetadataLock) {
for (AudioPresentation audioPresentation : audioPresentations) {
if (audioPresentation.getPresentationId() == presentationId) {
return true;
}
}
return false;
}
}
/**
* Returns a list of audio presentations.
*
* @return the list of audio presentations.
* Returns empty AudioPresentation list if no presentations are available.
*/
public List<AudioPresentation> getAudioPresentations() {
synchronized (mMetadataLock) {
if (mAudioPresentations == null) {
return new ArrayList<AudioPresentation>();
}
return new ArrayList<AudioPresentation>(mAudioPresentations);
}
}
/**
* Returns the program ID of the selected audio presentation.
*
* @return The ID of the program providing the selected audio presentation.
* Returns {@value AudioPresentation.PROGRAM_ID_UNKNOWN} if no audio presentation has
* been selected from a program.
* @see #selectAudioPresentation
*/
public int getSelectedProgramId() {
synchronized (mMetadataLock) {
return mSelectedAudioProgramId;
}
}
/**
* Returns the presentation ID of the selected audio presentation.
*
* @return The ID of the selected audio presentation.
* Returns {@value AudioPresentation.PRESENTATION_ID_UNKNOWN} if no audio presentation
* has been selected.
* @see #selectAudioPresentation
*/
public int getSelectedAudioPresentationId() {
synchronized (mMetadataLock) {
return mSelectedAudioPresentationId;
}
}
/**
* Responds to onAudioPresentationsChanged() and updates the internal audio presentation
* information.
* @return true if there is an update.
*/
boolean updateAudioPresentations(List<AudioPresentation> audioPresentations) {
synchronized (mMetadataLock) {
mAudioPresentations.clear();
for (AudioPresentation presentation : audioPresentations) {
mAudioPresentations.add(presentation);
}
return !mAudioPresentations.isEmpty();
}
}
/**
* Responds to onAudioPresentationSelected() and updates the internal audio presentation
* selection information.
* @return true if there is an update.
*/
boolean updateAudioPresentationSelection(int presentationId, int programId) {
synchronized (mMetadataLock) {
if ((programId != mSelectedAudioProgramId)
|| (presentationId != mSelectedAudioPresentationId)) {
mSelectedAudioPresentationId = presentationId;
mSelectedAudioProgramId = programId;
return true;
}
}
return false;
}
/**
* Selects a track.
*

93
media/java/android/media/tv/TvInputService.java Executable file → Normal file
View File

@@ -31,6 +31,7 @@ import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.net.Uri;
import android.os.AsyncTask;
@@ -738,6 +739,74 @@ public abstract class TvInputService extends Service {
});
}
/**
* Sends an updated list of all audio presentations available from a Next Generation Audio
* service. This is used by the framework to maintain the audio presentation information for
* a given track of {@link TvTrackInfo#TYPE_AUDIO}, which in turn is used by
* {@link TvView#getAudioPresentations} for the application to retrieve metadata for the
* current audio track. The TV input service must call this method as soon as the audio
* track presentation information becomes available or is updated. Note that in a case
* where a part of the information for the current track is updated, it is not necessary
* to create a new {@link TvTrackInfo} object with a different track ID.
*
* @param audioPresentations A list of audio presentation information pertaining to the
* selected track.
*/
public void notifyAudioPresentationChanged(@NonNull final List<AudioPresentation>
audioPresentations) {
final List<AudioPresentation> ap = new ArrayList<>(audioPresentations);
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "notifyAudioPresentationsChanged");
}
if (mSessionCallback != null) {
mSessionCallback.onAudioPresentationsChanged(ap);
}
} catch (RemoteException e) {
Log.e(TAG, "error in notifyAudioPresentationsChanged", e);
}
}
});
}
/**
* Sends the presentation and program IDs of the selected audio presentation. This is used
* to inform the application that a specific audio presentation is selected. The TV input
* service must call this method as soon as an audio presentation is selected either by
* default or in response to a call to {@link #onSelectTrack}. The selected audio
* presentation ID for a currently selected audio track is maintained in the framework until
* the next call to this method even after the entire audio presentation list for the track
* is updated (but is reset when the session is tuned to a new channel), so care must be
* taken not to result in an obsolete track audio presentation ID.
*
* @param presentationId The ID of the selected audio presentation for the current track.
* @param programId The ID of the program providing the selected audio presentation.
* @see #onSelectAudioPresentation
*/
public void notifyAudioPresentationSelected(final int presentationId, final int programId) {
executeOrPostRunnableOnMainThread(new Runnable() {
@MainThread
@Override
public void run() {
try {
if (DEBUG) {
Log.d(TAG, "notifyAudioPresentationSelected");
}
if (mSessionCallback != null) {
mSessionCallback.onAudioPresentationSelected(presentationId, programId);
}
} catch (RemoteException e) {
Log.e(TAG, "error in notifyAudioPresentationSelected", e);
}
}
});
}
/**
* Informs the application that the user is allowed to watch the current program content.
*
@@ -1245,6 +1314,23 @@ public abstract class TvInputService extends Service {
public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
}
/**
* Selects an audio presentation.
*
* <p>On successfully selecting the audio presentation,
* {@link #notifyAudioPresentationSelected} is invoked to provide updated information about
* the selected audio presentation to applications.
*
* @param presentationId The ID of the audio presentation to select.
* @param programId The ID of the program providing the selected audio presentation.
* @return {@code true} if the audio presentation selection was successful,
* {@code false} otherwise.
* @see #notifyAudioPresentationSelected
*/
public boolean onSelectAudioPresentation(int presentationId, int programId) {
return false;
}
/**
* Processes a private command sent from the application to the TV input. This can be used
* to provide domain-specific features that are only known between certain TV inputs and
@@ -1578,6 +1664,13 @@ public abstract class TvInputService extends Service {
onSetCaptionEnabled(enabled);
}
/**
* Calls {@link #onSelectAudioPresentation}.
*/
void selectAudioPresentation(int presentationId, int programId) {
onSelectAudioPresentation(presentationId, programId);
}
/**
* Calls {@link #onSelectTrack}.
*/

View File

@@ -30,6 +30,7 @@ import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.TvInputManager.Session;
import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
@@ -54,6 +55,7 @@ import android.view.ViewRootImpl;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
@@ -433,6 +435,35 @@ public class TvView extends ViewGroup {
}
}
/**
* Selects an audio presentation.
*
* @param presentationId The ID of the audio presentation to select.
* @param programId The ID of the program providing the selected audio presentation.
* @see #getAudioPresentations
*/
public void selectAudioPresentation(int presentationId, int programId) {
if (mSession != null) {
mSession.selectAudioPresentation(presentationId, programId);
}
}
/**
* Returns the list of audio presentations from the selected track of type
* {@link TvTrackInfo#TYPE_AUDIO}.
*
* @return the list of audio presentations from the selected audio track, or an empty list if no
* audio presentations are available.
* @see #selectAudioPresentation
*/
@NonNull
public List<AudioPresentation> getAudioPresentations() {
if (mSession == null) {
return new ArrayList<AudioPresentation>();
}
return mSession.getAudioPresentations();
}
/**
* Selects a track.
*
@@ -976,6 +1007,27 @@ public class TvView extends ViewGroup {
public void onChannelRetuned(String inputId, Uri channelUri) {
}
/**
* This is called when the audio presentation information has been changed.
*
* @param inputId The ID of the TV input bound to this view.
* @param audioPresentations A list of updated audio presentation information.
*/
public void onAudioPresentationsChanged(@NonNull String inputId,
@NonNull List<AudioPresentation> audioPresentations) {
}
/**
* This is called when audio presentation selection has changed.
*
* @param inputId The ID of the TV input bound to this view.
* @param presentationId The ID of the audio presentation selected.
* @param programId The ID of the program providing the selected audio presentation.
*/
public void onAudioPresentationSelected(@NonNull String inputId, int presentationId,
int programId) {
}
/**
* This is called when the track information has been changed.
*
@@ -1241,6 +1293,37 @@ public class TvView extends ViewGroup {
}
}
@Override
public void onAudioPresentationsChanged(Session session,
List<AudioPresentation> audioPresentations) {
if (DEBUG) {
Log.d(TAG, "onAudioPresentationsChanged(" + audioPresentations + ")");
}
if (this != mSessionCallback) {
Log.w(TAG, "onAudioPresentationsChanged - session not created");
return;
}
if (mCallback != null) {
mCallback.onAudioPresentationsChanged(mInputId, audioPresentations);
}
}
@Override
public void onAudioPresentationSelected(Session session, int presentationId,
int programId) {
if (DEBUG) {
Log.d(TAG, "onAudioPresentationSelected(presentationId=" + presentationId
+ ", programId=" + programId + ")");
}
if (this != mSessionCallback) {
Log.w(TAG, "onAudioPresentationSelected - session not created");
return;
}
if (mCallback != null) {
mCallback.onAudioPresentationSelected(mInputId, presentationId, programId);
}
}
@Override
public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
if (DEBUG) {

View File

@@ -43,6 +43,7 @@ import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioPresentation;
import android.media.PlaybackParams;
import android.media.tv.AdBuffer;
import android.media.tv.AdRequest;
@@ -1836,6 +1837,28 @@ public final class TvInputManagerService extends SystemService {
}
@Override
public void selectAudioPresentation(IBinder sessionToken, int presentationId,
int programId, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
userId, "selectAudioPresentation");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
getSessionLocked(sessionToken, callingUid,
resolvedUserId).selectAudioPresentation(
presentationId, programId);
} catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in selectAudioPresentation", e);
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void selectTrack(IBinder sessionToken, int type, String trackId, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -3359,6 +3382,43 @@ public final class TvInputManagerService extends SystemService {
}
}
@Override
public void onAudioPresentationsChanged(List<AudioPresentation> audioPresentations) {
synchronized (mLock) {
if (DEBUG) {
Slog.d(TAG, "onAudioPresentationChanged(" + audioPresentations + ")");
}
if (mSessionState.session == null || mSessionState.client == null) {
return;
}
try {
mSessionState.client.onAudioPresentationsChanged(audioPresentations,
mSessionState.seq);
} catch (RemoteException e) {
Slog.e(TAG, "error in onAudioPresentationsChanged", e);
}
}
}
@Override
public void onAudioPresentationSelected(int presentationId, int programId) {
synchronized (mLock) {
if (DEBUG) {
Slog.d(TAG, "onAudioPresentationSelected(presentationId=" + presentationId
+ ", programId=" + programId + ")");
}
if (mSessionState.session == null || mSessionState.client == null) {
return;
}
try {
mSessionState.client.onAudioPresentationSelected(presentationId, programId,
mSessionState.seq);
} catch (RemoteException e) {
Slog.e(TAG, "error in onAudioPresentationSelected", e);
}
}
}
@Override
public void onTracksChanged(List<TvTrackInfo> tracks) {
synchronized (mLock) {