aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 05:28:41 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 05:28:41 +0000
commit144e523475a62ade6d374f7593944acbc1cf18f9 (patch)
tree08aaccf8fde3dc86be6785b36e8729cb56e9b47e
parent71787acd27820b4069e50c3e859ba468391de505 (diff)
parent489f952460ffe2ef1eb1348b2083bba4de88567f (diff)
downloadTV-aml_tz5_341510010.tar.gz
Snap for 10453563 from 489f952460ffe2ef1eb1348b2083bba4de88567f to mainline-tzdata5-releaseaml_tz5_341510070aml_tz5_341510050aml_tz5_341510010aml_tz5_341510010
Change-Id: I12bfc149c0620ef8812e0480b5bec0f1d4b1e08b
-rw-r--r--interactive/SampleTvInteractiveAppService/AndroidManifest.xml1
-rw-r--r--interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml3
-rw-r--r--interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java539
-rw-r--r--src/com/android/tv/MainActivity.java21
-rwxr-xr-xsrc/com/android/tv/dialog/InteractiveAppDialogFragment.java4
-rw-r--r--src/com/android/tv/interactive/IAppManager.java68
-rw-r--r--src/com/android/tv/receiver/AudioCapabilitiesReceiver.java3
7 files changed, 627 insertions, 12 deletions
diff --git a/interactive/SampleTvInteractiveAppService/AndroidManifest.xml b/interactive/SampleTvInteractiveAppService/AndroidManifest.xml
index d631b956..72cd22f9 100644
--- a/interactive/SampleTvInteractiveAppService/AndroidManifest.xml
+++ b/interactive/SampleTvInteractiveAppService/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-permission android:name="com.google.android.dtvprovider.permission.READ" />
<uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
diff --git a/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml b/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml
index d8659f9b..915c3526 100644
--- a/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml
+++ b/interactive/SampleTvInteractiveAppService/res/layout/sample_layout.xml
@@ -59,5 +59,8 @@
<TextView
style="@style/overlay_text_item"
android:id="@+id/subtitle_track_selected"/>
+ <TextView
+ style="@style/overlay_text_item"
+ android:id="@+id/log_text"/>
</LinearLayout>
</RelativeLayout> \ No newline at end of file
diff --git a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java
index 83b16ffc..d85ab776 100644
--- a/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java
+++ b/interactive/SampleTvInteractiveAppService/src/com/android/tv/samples/sampletvinteractiveappservice/TiasSessionImpl.java
@@ -16,28 +16,56 @@
package com.android.tv.samples.sampletvinteractiveappservice;
+import android.annotation.TargetApi;
import android.app.Presentation;
import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
+import android.media.MediaPlayer;
+import android.media.tv.AdRequest;
+import android.media.tv.AdResponse;
+import android.media.tv.BroadcastInfoRequest;
+import android.media.tv.BroadcastInfoResponse;
+import android.media.tv.SectionRequest;
+import android.media.tv.SectionResponse;
+import android.media.tv.StreamEventRequest;
+import android.media.tv.StreamEventResponse;
+import android.media.tv.TableRequest;
+import android.media.tv.TableResponse;
import android.media.tv.TvTrackInfo;
+import android.media.tv.interactive.AppLinkInfo;
import android.media.tv.interactive.TvInteractiveAppManager;
import android.media.tv.interactive.TvInteractiveAppService;
import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
+import android.widget.VideoView;
import androidx.annotation.NonNull;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
public class TiasSessionImpl extends TvInteractiveAppService.Session {
@@ -46,7 +74,11 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
private static final String VIRTUAL_DISPLAY_NAME = "sample_tias_display";
+ // For testing purposes, limit the number of response for a single request
+ private static final int MAX_HANDLED_RESPONSE = 3;
+
private final Context mContext;
+ private TvInteractiveAppManager mTvIAppManager;
private final Handler mHandler;
private final String mAppServiceId;
private final int mType;
@@ -60,6 +92,27 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
private TextView mVideoTrackView;
private TextView mAudioTrackView;
private TextView mSubtitleTrackView;
+ private TextView mLogView;
+
+ private VideoView mVideoView;
+ private SurfaceView mAdSurfaceView;
+ private Surface mAdSurface;
+ private ParcelFileDescriptor mAdFd;
+ private FrameLayout mMediaContainer;
+ private int mAdState;
+ private int mWidth;
+ private int mHeight;
+ private int mScreenWidth;
+ private int mScreenHeight;
+ private String mCurrentTvInputId;
+ private Uri mCurrentChannelUri;
+ private String mSelectingAudioTrackId;
+ private String mFirstAudioTrackId;
+ private int mGeneratedRequestId = 0;
+ private boolean mRequestStreamEventFinished = false;
+ private int mSectionReceived = 0;
+ private List<String> mStreamDataList = new ArrayList<>();
+ private boolean mIsFullScreen = true;
public TiasSessionImpl(Context context, String iAppServiceId, int type) {
super(context);
@@ -71,12 +124,68 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
mAppServiceId = iAppServiceId;
mType = type;
mHandler = new Handler(context.getMainLooper());
+ mTvIAppManager = (TvInteractiveAppManager) mContext.getSystemService(
+ Context.TV_INTERACTIVE_APP_SERVICE);
mViewContainer = new LinearLayout(context);
mViewContainer.setBackground(new ColorDrawable(0));
}
@Override
+ public View onCreateMediaView() {
+ mAdSurfaceView = new SurfaceView(mContext);
+ if (DEBUG) {
+ Log.d(TAG, "create surfaceView");
+ }
+ mAdSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+ mAdSurfaceView
+ .getHolder()
+ .addCallback(
+ new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mAdSurface = holder.getSurface();
+ }
+
+ @Override
+ public void surfaceChanged(
+ SurfaceHolder holder, int format, int width, int height) {
+ mAdSurface = holder.getSurface();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {}
+ });
+ mAdSurfaceView.setVisibility(View.INVISIBLE);
+ ViewGroup.LayoutParams layoutParams =
+ new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ mAdSurfaceView.setLayoutParams(layoutParams);
+ mMediaContainer.addView(mVideoView);
+ mMediaContainer.addView(mAdSurfaceView);
+ return mMediaContainer;
+ }
+
+ @Override
+ public void onAdResponse(AdResponse adResponse) {
+ mAdState = adResponse.getResponseType();
+ switch (mAdState) {
+ case AdResponse.RESPONSE_TYPE_PLAYING:
+ long time = adResponse.getElapsedTimeMillis();
+ updateLogText("AD is playing. " + time);
+ break;
+ case AdResponse.RESPONSE_TYPE_STOPPED:
+ updateLogText("AD is stopped.");
+ mAdSurfaceView.setVisibility(View.INVISIBLE);
+ break;
+ case AdResponse.RESPONSE_TYPE_FINISHED:
+ updateLogText("AD is play finished.");
+ mAdSurfaceView.setVisibility(View.INVISIBLE);
+ break;
+ }
+ }
+
+ @Override
public void onRelease() {
if (DEBUG) {
Log.d(TAG, "onRelease");
@@ -99,6 +208,7 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
if (mSurface != null) {
mSurface.release();
}
+ updateSurface(surface, mWidth, mHeight);
mSurface = surface;
return true;
}
@@ -111,6 +221,8 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
}
if (mSurface != null) {
updateSurface(mSurface, width, height);
+ mWidth = width;
+ mHeight = height;
}
}
@@ -122,6 +234,7 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
mHandler.post(
() -> {
initSampleView();
+ setMediaViewEnabled(true);
requestCurrentTvInputId();
requestCurrentChannelUri();
requestTrackInfoList();
@@ -151,10 +264,148 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+ // TODO: use a menu view instead of key events for the following tests
switch (keyCode) {
case KeyEvent.KEYCODE_PROG_RED:
tuneToNextChannel();
return true;
+ case KeyEvent.KEYCODE_A:
+ updateLogText("stop video broadcast begin");
+ tuneChannelByType(
+ TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_STOP,
+ mCurrentTvInputId,
+ null);
+ updateLogText("stop video broadcast end");
+ return true;
+ case KeyEvent.KEYCODE_B:
+ updateLogText("resume video broadcast begin");
+ tuneChannelByType(
+ TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE,
+ mCurrentTvInputId,
+ mCurrentChannelUri);
+ updateLogText("resume video broadcast end");
+ return true;
+ case KeyEvent.KEYCODE_C:
+ updateLogText("unselect audio track");
+ mSelectingAudioTrackId = null;
+ selectTrack(TvTrackInfo.TYPE_AUDIO, null);
+ return true;
+ case KeyEvent.KEYCODE_D:
+ updateLogText("select audio track " + mFirstAudioTrackId);
+ mSelectingAudioTrackId = mFirstAudioTrackId;
+ selectTrack(TvTrackInfo.TYPE_AUDIO, mFirstAudioTrackId);
+ return true;
+ case KeyEvent.KEYCODE_E:
+ if (mVideoView != null) {
+ if (mVideoView.isPlaying()) {
+ updateLogText("stop media");
+ mVideoView.stopPlayback();
+ mVideoView.setVisibility(View.GONE);
+ tuneChannelByType(
+ TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE,
+ mCurrentTvInputId,
+ mCurrentChannelUri);
+ } else {
+ updateLogText("play media");
+ tuneChannelByType(
+ TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_STOP,
+ mCurrentTvInputId,
+ null);
+ mVideoView.setVisibility(View.VISIBLE);
+ // TODO: put a file sample.mp4 in res/raw/ and use R.raw.sample for the URI
+ Uri uri = Uri.parse(
+ "android.resource://" + mContext.getPackageName() + "/");
+ mVideoView.setVideoURI(uri);
+ mVideoView.start();
+ updateLogText("media is playing");
+ }
+ }
+ return true;
+ case KeyEvent.KEYCODE_F:
+ updateLogText("request StreamEvent");
+ mRequestStreamEventFinished = false;
+ mStreamDataList.clear();
+ // TODO: build target URI instead of using channel URI
+ requestStreamEvent(
+ mCurrentChannelUri == null ? null : mCurrentChannelUri.toString(),
+ "event1");
+ return true;
+ case KeyEvent.KEYCODE_G:
+ updateLogText("change video bounds");
+ if (mIsFullScreen) {
+ setVideoBounds(new Rect(100, 150, 960, 540));
+ updateLogText("Change video broadcast size(100, 150, 960, 540)");
+ mIsFullScreen = false;
+ } else {
+ setVideoBounds(new Rect(0, 0, mScreenWidth, mScreenHeight));
+ updateLogText("Change video broadcast full screen");
+ mIsFullScreen = true;
+ }
+ return true;
+ case KeyEvent.KEYCODE_H:
+ updateLogText("request section");
+ mSectionReceived = 0;
+ requestSection(false, 0, 0x0, -1);
+ return true;
+ case KeyEvent.KEYCODE_I:
+ if (mTvIAppManager == null) {
+ updateLogText("TvIAppManager null");
+ return false;
+ }
+ List<AppLinkInfo> appLinks = getAppLinkInfoList();
+ if (appLinks.isEmpty()) {
+ updateLogText("Not found AppLink");
+ } else {
+ AppLinkInfo appLink = appLinks.get(0);
+ Intent intent = new Intent();
+ intent.setComponent(appLink.getComponentName());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.getApplicationContext().startActivity(intent);
+ updateLogText("Launch " + appLink.getComponentName());
+ }
+ return true;
+ case KeyEvent.KEYCODE_J:
+ updateLogText("Request SI Tables ");
+ // Network Information Table (NIT)
+ requestTable(false, 0x40, /* TableRequest.TABLE_NAME_NIT */ 3, -1);
+ // Service Description Table (SDT)
+ requestTable(false, 0x42, /* TableRequest.TABLE_NAME_SDT */ 5, -1);
+ // Event Information Table (EIT)
+ requestTable(false, 0x4e, /* TableRequest.TABLE_NAME_EIT */ 6, -1);
+ return true;
+ case KeyEvent.KEYCODE_K:
+ updateLogText("Request Video Bounds");
+ requestCurrentVideoBoundsWrapper();
+ return true;
+ case KeyEvent.KEYCODE_L: {
+ updateLogText("stop video broadcast with blank mode");
+ Bundle params = new Bundle();
+ params.putInt(
+ /* TvInteractiveAppService.COMMAND_PARAMETER_KEY_STOP_MODE */
+ "command_stop_mode",
+ /* TvInteractiveAppService.COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK */
+ 1);
+ tuneChannelByType(TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_STOP,
+ mCurrentTvInputId, null, params);
+ return true;
+ }
+ case KeyEvent.KEYCODE_M: {
+ updateLogText("stop video broadcast with freeze mode");
+ Bundle params = new Bundle();
+ params.putInt(
+ /* TvInteractiveAppService.COMMAND_PARAMETER_KEY_STOP_MODE */
+ "command_stop_mode",
+ /* TvInteractiveAppService.COMMAND_PARAMETER_VALUE_STOP_MODE_FREEZE */
+ 2);
+ tuneChannelByType(TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_STOP,
+ mCurrentTvInputId, null, params);
+ return true;
+ }
+ case KeyEvent.KEYCODE_N: {
+ updateLogText("request AD");
+ requestAd();
+ return true;
+ }
default:
return super.onKeyDown(keyCode, event);
}
@@ -164,12 +415,33 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_PROG_RED:
+ case KeyEvent.KEYCODE_A:
+ case KeyEvent.KEYCODE_B:
+ case KeyEvent.KEYCODE_C:
+ case KeyEvent.KEYCODE_D:
+ case KeyEvent.KEYCODE_E:
+ case KeyEvent.KEYCODE_F:
+ case KeyEvent.KEYCODE_G:
+ case KeyEvent.KEYCODE_H:
+ case KeyEvent.KEYCODE_I:
+ case KeyEvent.KEYCODE_J:
+ case KeyEvent.KEYCODE_K:
+ case KeyEvent.KEYCODE_L:
+ case KeyEvent.KEYCODE_M:
+ case KeyEvent.KEYCODE_N:
return true;
default:
return super.onKeyUp(keyCode, event);
}
}
+ public void updateLogText(String log) {
+ if (DEBUG) {
+ Log.d(TAG, log);
+ }
+ mLogView.setText(log);
+ }
+
private void updateSurface(Surface surface, int width, int height) {
mHandler.post(
() -> {
@@ -210,11 +482,32 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
mVideoTrackView = sampleView.findViewById(R.id.video_track_selected);
mAudioTrackView = sampleView.findViewById(R.id.audio_track_selected);
mSubtitleTrackView = sampleView.findViewById(R.id.subtitle_track_selected);
+ mLogView = sampleView.findViewById(R.id.log_text);
// Set default values for the selected tracks, since we cannot request data on them directly
mVideoTrackView.setText("No video track selected");
mAudioTrackView.setText("No audio track selected");
mSubtitleTrackView.setText("No subtitle track selected");
+ mVideoView = new VideoView(mContext);
+ mVideoView.setVisibility(View.GONE);
+ mVideoView.setOnCompletionListener(
+ new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ mVideoView.setVisibility(View.GONE);
+ mLogView.setText("MediaPlayer onCompletion");
+ tuneChannelByType(
+ TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE,
+ mCurrentTvInputId,
+ mCurrentChannelUri);
+ }
+ });
+ mWidth = 0;
+ mHeight = 0;
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mScreenWidth = wm.getDefaultDisplay().getWidth();
+ mScreenHeight = wm.getDefaultDisplay().getHeight();
+
mViewContainer.addView(sampleView);
}
@@ -267,9 +560,15 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
);
}
- private void tuneToNextChannel() {
- sendPlaybackCommandRequest(TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE_NEXT,
- null);
+ private void tuneChannelByType(String type, String inputId, Uri channelUri, Bundle bundle) {
+ Bundle parameters = bundle == null ? new Bundle() : bundle;
+ if (TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE.equals(type)) {
+ parameters.putString(
+ TvInteractiveAppService.COMMAND_PARAMETER_KEY_CHANNEL_URI,
+ channelUri == null ? null : channelUri.toString());
+ parameters.putString(TvInteractiveAppService.COMMAND_PARAMETER_KEY_INPUT_ID, inputId);
+ }
+ mHandler.post(() -> sendPlaybackCommandRequest(type, parameters));
// Delay request for new information to give time to tune
mHandler.postDelayed(
() -> {
@@ -281,11 +580,20 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
);
}
+ private void tuneChannelByType(String type, String inputId, Uri channelUri) {
+ tuneChannelByType(type, inputId, channelUri, new Bundle());
+ }
+
+ private void tuneToNextChannel() {
+ tuneChannelByType(TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_TUNE_NEXT, null, null);
+ }
+
@Override
public void onCurrentChannelUri(Uri channelUri) {
if (DEBUG) {
Log.d(TAG, "onCurrentChannelUri uri=" + channelUri);
}
+ mCurrentChannelUri = channelUri;
mChannelUriView.setText("Channel URI: " + channelUri);
}
@@ -301,6 +609,12 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
}
}
}
+ for (TvTrackInfo info : tracks) {
+ if (info.getType() == TvTrackInfo.TYPE_AUDIO) {
+ mFirstAudioTrackId = info.getId();
+ break;
+ }
+ }
mTracks = tracks;
}
@@ -318,6 +632,14 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
Log.d(TAG, "onTrackSelected type=" + type + " trackId=" + trackId);
}
updateTrackSelectedView(type, trackId);
+
+ if (TextUtils.equals(mSelectingAudioTrackId, trackId)) {
+ if (mSelectingAudioTrackId == null) {
+ updateLogText("unselect audio succeed");
+ } else {
+ updateLogText("select audio succeed");
+ }
+ }
}
@Override
@@ -325,6 +647,217 @@ public class TiasSessionImpl extends TvInteractiveAppService.Session {
if (DEBUG) {
Log.d(TAG, "onCurrentTvInputId id=" + inputId);
}
+ mCurrentTvInputId = inputId;
mTvInputIdView.setText("TV Input ID: " + inputId);
}
+
+ @Override
+ public void onTuned(Uri channelUri) {
+ mCurrentChannelUri = channelUri;
+ }
+
+ @Override
+ public void onCurrentVideoBounds(@NonNull Rect bounds) {
+ updateLogText("Received video Bounds " + bounds.toShortString());
+ }
+
+ @Override
+ public void onBroadcastInfoResponse(BroadcastInfoResponse response) {
+ if (mGeneratedRequestId == response.getRequestId()) {
+ if (!mRequestStreamEventFinished && response instanceof StreamEventResponse) {
+ handleStreamEventResponse((StreamEventResponse) response);
+ } else if (mSectionReceived < MAX_HANDLED_RESPONSE
+ && response instanceof SectionResponse) {
+ handleSectionResponse((SectionResponse) response);
+ } else if (response instanceof TableResponse) {
+ handleTableResponse((TableResponse) response);
+ }
+ }
+ }
+
+ private void handleSectionResponse(SectionResponse response) {
+ mSectionReceived++;
+ byte[] data = null;
+ Bundle params = response.getSessionData();
+ if (params != null) {
+ // TODO: define the key
+ data = params.getByteArray("key_raw_data");
+ }
+ int version = response.getVersion();
+ updateLogText(
+ "Received section data version = "
+ + version
+ + ", data = "
+ + Arrays.toString(data));
+ }
+
+ private void handleStreamEventResponse(StreamEventResponse response) {
+ updateLogText("Received stream event response");
+ byte[] rData = response.getData();
+ if (rData == null) {
+ mRequestStreamEventFinished = true;
+ updateLogText("Received stream event data is null");
+ return;
+ }
+ // TODO: convert to Hex instead
+ String data = Arrays.toString(rData);
+ if (mStreamDataList.contains(data)) {
+ return;
+ }
+ mStreamDataList.add(data);
+ updateLogText(
+ "Received stream event data("
+ + (mStreamDataList.size() - 1)
+ + "): "
+ + data);
+ if (mStreamDataList.size() >= MAX_HANDLED_RESPONSE) {
+ mRequestStreamEventFinished = true;
+ updateLogText("Received stream event data finished");
+ }
+ }
+
+ private void handleTableResponse(TableResponse response) {
+ updateLogText(
+ "Received table data version = "
+ + response.getVersion()
+ + ", size="
+ + response.getSize()
+ + ", requestId="
+ + response.getRequestId()
+ + ", data = "
+ + Arrays.toString(getTableByteArray(response)));
+ }
+
+ private void selectTrack(int type, String trackId) {
+ Bundle params = new Bundle();
+ params.putInt(TvInteractiveAppService.COMMAND_PARAMETER_KEY_TRACK_TYPE, type);
+ params.putString(TvInteractiveAppService.COMMAND_PARAMETER_KEY_TRACK_ID, trackId);
+ mHandler.post(
+ () ->
+ sendPlaybackCommandRequest(
+ TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_SELECT_TRACK,
+ params));
+ }
+
+ private int generateRequestId() {
+ return ++mGeneratedRequestId;
+ }
+
+ private void requestStreamEvent(String targetUri, String eventName) {
+ if (targetUri == null) {
+ return;
+ }
+ int requestId = generateRequestId();
+ BroadcastInfoRequest request =
+ new StreamEventRequest(
+ requestId,
+ BroadcastInfoRequest.REQUEST_OPTION_AUTO_UPDATE,
+ Uri.parse(targetUri),
+ eventName);
+ requestBroadcastInfo(request);
+ }
+
+ private void requestSection(boolean repeat, int tsPid, int tableId, int version) {
+ int requestId = generateRequestId();
+ BroadcastInfoRequest request =
+ new SectionRequest(
+ requestId,
+ repeat ?
+ BroadcastInfoRequest.REQUEST_OPTION_REPEAT :
+ BroadcastInfoRequest.REQUEST_OPTION_AUTO_UPDATE,
+ tsPid,
+ tableId,
+ version);
+ requestBroadcastInfo(request);
+ }
+
+ private void requestTable(boolean repeat, int tableId, int tableName, int version) {
+ int requestId = generateRequestId();
+ BroadcastInfoRequest request =
+ new TableRequest(
+ requestId,
+ repeat
+ ? BroadcastInfoRequest.REQUEST_OPTION_REPEAT
+ : BroadcastInfoRequest.REQUEST_OPTION_AUTO_UPDATE,
+ tableId,
+ tableName,
+ version);
+ requestBroadcastInfo(request);
+ }
+
+ public void requestAd() {
+ try {
+ // TODO: add the AD file to this project
+ RandomAccessFile adiFile =
+ new RandomAccessFile(
+ mContext.getApplicationContext().getFilesDir() + "/ad.mp4", "r");
+ mAdFd = ParcelFileDescriptor.dup(adiFile.getFD());
+ } catch (Exception e) {
+ updateLogText("open advertisement file failed. " + e.getMessage());
+ return;
+ }
+ long startTime = 20000;
+ long stopTime = startTime + 25000;
+ long echoInterval = 1000;
+ String mediaFileType = "MP4";
+ mHandler.post(
+ () -> {
+ AdRequest adRequest;
+ if (mAdState == AdResponse.RESPONSE_TYPE_PLAYING) {
+ updateLogText("RequestAd stop");
+ adRequest =
+ new AdRequest(
+ mGeneratedRequestId,
+ AdRequest.REQUEST_TYPE_STOP,
+ null,
+ 0,
+ 0,
+ 0,
+ null,
+ null);
+ } else {
+ updateLogText("RequestAd start");
+ int requestId = generateRequestId();
+ mAdSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+ mAdSurfaceView.setVisibility(View.VISIBLE);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable("dai_surface", mAdSurface);
+ adRequest =
+ new AdRequest(
+ requestId,
+ AdRequest.REQUEST_TYPE_START,
+ mAdFd,
+ startTime,
+ stopTime,
+ echoInterval,
+ mediaFileType,
+ bundle);
+ }
+ requestAd(adRequest);
+ });
+ }
+
+ @TargetApi(34)
+ private List<AppLinkInfo> getAppLinkInfoList() {
+ if (Build.VERSION.SDK_INT < 34 || mTvIAppManager == null) {
+ return new ArrayList<>();
+ }
+ return mTvIAppManager.getAppLinkInfoList();
+ }
+
+ @TargetApi(34)
+ private void requestCurrentVideoBoundsWrapper() {
+ if (Build.VERSION.SDK_INT < 34) {
+ return;
+ }
+ requestCurrentVideoBounds();
+ }
+
+ @TargetApi(34)
+ private byte[] getTableByteArray(TableResponse response) {
+ if (Build.VERSION.SDK_INT < 34) {
+ return null;
+ }
+ return response.getTableByteArray();
+ }
}
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index a7d59035..cea293de 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -42,6 +42,7 @@ import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.media.tv.TvTrackInfo;
import android.media.tv.TvView.OnUnhandledInputEventListener;
+import android.media.tv.interactive.TvInteractiveAppManager;
import android.media.tv.interactive.TvInteractiveAppView;
import android.net.Uri;
import android.os.Build;
@@ -183,7 +184,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -262,6 +262,9 @@ public class MainActivity extends Activity
SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_ON);
SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
+ if (Build.VERSION.SDK_INT > 33) { // TIRAMISU
+ SYSTEM_INTENT_FILTER.addAction(TvInteractiveAppManager.ACTION_APP_LINK_COMMAND);
+ }
}
private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
@@ -416,6 +419,13 @@ public class MainActivity extends Activity
tune(true);
}
break;
+ case TvInteractiveAppManager.ACTION_APP_LINK_COMMAND:
+ if (DEBUG) {
+ Log.d(TAG, "Received action link command");
+ }
+ // TODO: handle the command
+ break;
+
default: // fall out
}
}
@@ -753,8 +763,8 @@ public class MainActivity extends Activity
@TargetApi(Build.VERSION_CODES.TIRAMISU)
@Override
public void onInteractiveAppChecked(boolean checked) {
+ TvSettings.setTvIAppOn(getApplicationContext(), checked);
if (checked) {
- TvSettings.setTvIAppOn(getApplicationContext(), checked);
mIAppManager.processHeldAitInfo();
}
}
@@ -857,7 +867,7 @@ public class MainActivity extends Activity
mMainDurationTimer.start();
applyParentalControlSettings();
- registerReceiver(mBroadcastReceiver, SYSTEM_INTENT_FILTER);
+ registerReceiver(mBroadcastReceiver, SYSTEM_INTENT_FILTER, Context.RECEIVER_EXPORTED);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Intent notificationIntent = new Intent(this, NotificationService.class);
@@ -1452,6 +1462,9 @@ public class MainActivity extends Activity
if (DeveloperPreferences.LOG_KEYEVENT.get(this)) {
Log.d(TAG, "dispatchKeyEvent(" + event + ")");
}
+ if (mIAppManager != null && mIAppManager.dispatchKeyEvent(event)) {
+ return true;
+ }
// If an activity is closed on a back key down event, back key down events with none zero
// repeat count or a back key up event can be happened without the first back key down
// event which should be ignored in this activity.
@@ -2495,7 +2508,7 @@ public class MainActivity extends Activity
return handled;
}
- private boolean isKeyEventBlocked() {
+ public boolean isKeyEventBlocked() {
// If the current channel is a passthrough channel, we don't handle the key events in TV
// activity. Instead, the key event will be handled by the passthrough TV input.
return mChannelTuner.isCurrentChannelPassthrough();
diff --git a/src/com/android/tv/dialog/InteractiveAppDialogFragment.java b/src/com/android/tv/dialog/InteractiveAppDialogFragment.java
index 70adbe30..c5ffbaac 100755
--- a/src/com/android/tv/dialog/InteractiveAppDialogFragment.java
+++ b/src/com/android/tv/dialog/InteractiveAppDialogFragment.java
@@ -16,10 +16,11 @@
package com.android.tv.dialog;
+import android.annotation.TargetApi;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.media.tv.AitInfo;
+import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,6 +35,7 @@ import java.util.function.Function;
import dagger.android.AndroidInjection;
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
public class InteractiveAppDialogFragment extends SafeDismissDialogFragment {
private static final boolean DEBUG = false;
diff --git a/src/com/android/tv/interactive/IAppManager.java b/src/com/android/tv/interactive/IAppManager.java
index 29de5930..682b35c6 100644
--- a/src/com/android/tv/interactive/IAppManager.java
+++ b/src/com/android/tv/interactive/IAppManager.java
@@ -33,7 +33,10 @@ import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
import android.view.View;
+import android.view.ViewGroup;
import com.android.tv.MainActivity;
import com.android.tv.R;
@@ -87,6 +90,27 @@ public class IAppManager {
executor,
new MyInteractiveAppViewCallback()
);
+ mTvIAppView.setOnUnhandledInputEventListener(executor,
+ inputEvent -> {
+ if (mMainActivity.isKeyEventBlocked()) {
+ return true;
+ }
+ if (inputEvent instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent) inputEvent;
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
+ && keyEvent.isLongPress()) {
+ if (mMainActivity.onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
+ return true;
+ }
+ }
+ if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+ return mMainActivity.onKeyUp(keyEvent.getKeyCode(), keyEvent);
+ } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+ return mMainActivity.onKeyDown(keyEvent.getKeyCode(), keyEvent);
+ }
+ }
+ return false;
+ });
}
public void stop() {
@@ -104,6 +128,14 @@ public class IAppManager {
}
}
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mTvIAppView != null && mTvIAppView.getVisibility() == View.VISIBLE
+ && mTvIAppView.dispatchKeyEvent(event)){
+ return true;
+ }
+ return false;
+ }
+
public void onAitInfoUpdated(AitInfo aitInfo) {
if (mTvIAppManager == null || aitInfo == null) {
return;
@@ -204,7 +236,7 @@ public class IAppManager {
@Override
public void onPlaybackCommandRequest(String iAppServiceId, String cmdType,
Bundle parameters) {
- if (mTvView == null) {
+ if (mTvView == null || cmdType == null) {
return;
}
switch (cmdType) {
@@ -261,6 +293,14 @@ public class IAppManager {
mHandler.post(mMainActivity::channelDown);
break;
case TvInteractiveAppService.PLAYBACK_COMMAND_TYPE_STOP:
+ int mode = 1; // TvInteractiveAppService.COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK
+ if (parameters != null) {
+ mode = parameters.getInt(
+ /* TvInteractiveAppService.COMMAND_PARAMETER_KEY_STOP_MODE */
+ "command_stop_mode",
+ /*TvInteractiveAppService.COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK*/
+ 1);
+ }
mHandler.post(mMainActivity::stopTv);
break;
default:
@@ -271,7 +311,8 @@ public class IAppManager {
}
@Override
- public void onStateChanged(String iAppServiceId, int state, int err) {}
+ public void onStateChanged(String iAppServiceId, int state, int err) {
+ }
@Override
public void onBiInteractiveAppCreated(String iAppServiceId, Uri biIAppUri,
@@ -281,7 +322,28 @@ public class IAppManager {
public void onTeletextAppStateChanged(String iAppServiceId, int state) {}
@Override
- public void onSetVideoBounds(String iAppServiceId, Rect rect) {}
+ public void onSetVideoBounds(String iAppServiceId, Rect rect) {
+ if (mTvView != null) {
+ ViewGroup.MarginLayoutParams layoutParams = mTvView.getTvViewLayoutParams();
+ layoutParams.setMargins(rect.left, rect.top, rect.right, rect.bottom);
+ mTvView.setTvViewLayoutParams(layoutParams);
+ }
+ }
+
+ @Override
+ @TargetApi(34)
+ public void onRequestCurrentVideoBounds(@NonNull String iAppServiceId) {
+ mHandler.post(
+ () -> {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestCurrentVideoBounds service ID = "
+ + iAppServiceId);
+ }
+ Rect bounds = new Rect(mTvView.getLeft(), mTvView.getTop(),
+ mTvView.getRight(), mTvView.getBottom());
+ mTvIAppView.sendCurrentVideoBounds(bounds);
+ });
+ }
@Override
public void onRequestCurrentChannelUri(String iAppServiceId) {
diff --git a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
index 5fa7606d..9578e243 100644
--- a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
+++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
@@ -67,7 +67,8 @@ public final class AudioCapabilitiesReceiver {
}
public void register() {
- mContext.registerReceiver(mReceiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
+ mContext.registerReceiver(mReceiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG),
+ Context.RECEIVER_EXPORTED);
}
public void unregister() {