Snap for 10453563 from 489f952460ffe2ef1eb1348b2083bba4de88567f to mainline-tzdata5-release

Change-Id: I12bfc149c0620ef8812e0480b5bec0f1d4b1e08b
diff --git a/interactive/SampleTvInteractiveAppService/AndroidManifest.xml b/interactive/SampleTvInteractiveAppService/AndroidManifest.xml
index d631b95..72cd22f 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 d8659f9..915c352 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 83b16ff..d85ab77 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 @@
 
     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 @@
     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 @@
         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 @@
         if (mSurface != null) {
             mSurface.release();
         }
+        updateSurface(surface, mWidth, mHeight);
         mSurface = surface;
         return true;
     }
@@ -111,6 +221,8 @@
         }
         if (mSurface != null) {
             updateSurface(mSurface, width, height);
+            mWidth = width;
+            mHeight = height;
         }
     }
 
@@ -122,6 +234,7 @@
         mHandler.post(
                 () -> {
                     initSampleView();
+                    setMediaViewEnabled(true);
                     requestCurrentTvInputId();
                     requestCurrentChannelUri();
                     requestTrackInfoList();
@@ -151,10 +264,148 @@
 
     @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 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 @@
         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 @@
         );
     }
 
-    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 @@
         );
     }
 
+    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 @@
                 }
             }
         }
+        for (TvTrackInfo info : tracks) {
+            if (info.getType() == TvTrackInfo.TYPE_AUDIO) {
+                mFirstAudioTrackId = info.getId();
+                break;
+            }
+        }
         mTracks = tracks;
     }
 
@@ -318,6 +632,14 @@
             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 @@
         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 a7d5903..cea293d 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -42,6 +42,7 @@
 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.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 @@
         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 @@
                                 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 @@
     @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 @@
         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 @@
         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 @@
         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 70adbe3..c5ffbaa 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 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 29de593..682b35c 100644
--- a/src/com/android/tv/interactive/IAppManager.java
+++ b/src/com/android/tv/interactive/IAppManager.java
@@ -33,7 +33,10 @@
 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 @@
                 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 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 @@
         @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 @@
                     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 @@
         }
 
         @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 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 5fa7606..9578e24 100644
--- a/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
+++ b/src/com/android/tv/receiver/AudioCapabilitiesReceiver.java
@@ -67,7 +67,8 @@
     }
 
     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() {