diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:28:41 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:28:41 +0000 |
commit | 144e523475a62ade6d374f7593944acbc1cf18f9 (patch) | |
tree | 08aaccf8fde3dc86be6785b36e8729cb56e9b47e | |
parent | 71787acd27820b4069e50c3e859ba468391de505 (diff) | |
parent | 489f952460ffe2ef1eb1348b2083bba4de88567f (diff) | |
download | TV-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
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() { |