Merge "Convert Android.mk to Android.bp" am: 1ea232f323 am: 2a2569c91f am: 2b1e4b4f3a

Original change: https://android-review.googlesource.com/c/platform/packages/apps/TV/+/1689287

Change-Id: I68ba36ffa35e17ceb61a33cca1ac033e344a6b97
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 854f288..2cff92d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.READ_TV_LISTINGS"/>
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA"/>
     <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
@@ -115,6 +116,7 @@
 
                 <category android:name="android.intent.category.LAUNCHER"/>
                 <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
+                <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
         <activity android:name="com.android.tv.MainActivity"
@@ -165,7 +167,12 @@
         <activity android:name="com.android.tv.SelectInputActivity"
              android:configChanges="keyboard|keyboardHidden"
              android:launchMode="singleTask"
-             android:theme="@style/Theme.SelectInputActivity"/>
+             android:theme="@style/Theme.SelectInputActivity">
+            <intent-filter>
+                <action android:name="com.android.tv.action.VIEW_INPUTS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity android:name="com.android.tv.onboarding.OnboardingActivity"
              android:configChanges="keyboard|keyboardHidden"
              android:launchMode="singleTop"
diff --git a/com.android.tv.xml b/com.android.tv.xml
index 245d275..5ac6c0c 100644
--- a/com.android.tv.xml
+++ b/com.android.tv.xml
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <permissions>
+    <feature name="com.google.android.tv.installed" />
+
     <privapp-permissions package="com.android.tv">
         <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
         <permission name="android.permission.DVB_DEVICE"/>
@@ -7,6 +9,7 @@
         <permission name="android.permission.HDMI_CEC"/>
         <permission name="android.permission.MODIFY_PARENTAL_CONTROLS"/>
         <permission name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
+        <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
         <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
         <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/>
     </privapp-permissions>
diff --git a/lint-baseline.xml b/lint-baseline.xml
index 2a8be31..d91a189 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -118,7 +118,7 @@
         errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/TV/src/com/android/tv/MainActivity.java"
-            line="529"
+            line="534"
             column="28"/>
     </issue>
 
@@ -129,7 +129,7 @@
         errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/TV/src/com/android/tv/MainActivity.java"
-            line="997"
+            line="1002"
             column="28"/>
     </issue>
 
@@ -140,7 +140,7 @@
         errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/TV/src/com/android/tv/MainActivity.java"
-            line="1024"
+            line="1029"
             column="48"/>
     </issue>
 
@@ -151,7 +151,7 @@
         errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/TV/src/com/android/tv/MainActivity.java"
-            line="1032"
+            line="1037"
             column="28"/>
     </issue>
 
@@ -162,7 +162,7 @@
         errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/TV/src/com/android/tv/MainActivity.java"
-            line="1060"
+            line="1065"
             column="28"/>
     </issue>
 
@@ -173,7 +173,7 @@
         errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/TV/src/com/android/tv/MainActivity.java"
-            line="1539"
+            line="1544"
             column="35"/>
     </issue>
 
@@ -184,7 +184,7 @@
         errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/TV/src/com/android/tv/MainActivity.java"
-            line="2397"
+            line="2402"
             column="27"/>
     </issue>
 
@@ -195,7 +195,7 @@
         errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="packages/apps/TV/src/com/android/tv/MainActivity.java"
-            line="2808"
+            line="2813"
             column="27"/>
     </issue>
 
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index 9c615b9..8dbafe4 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -149,6 +149,7 @@
 import com.android.tv.util.AsyncDbTask;
 import com.android.tv.util.AsyncDbTask.DbExecutor;
 import com.android.tv.util.CaptionSettings;
+import com.android.tv.util.GtvUtils;
 import com.android.tv.util.OnboardingUtils;
 import com.android.tv.util.SetupUtils;
 import com.android.tv.util.TvInputManagerHelper;
@@ -456,7 +457,11 @@
                 }
 
                 @Override
-                public void onChannelChanged(Channel previousChannel, Channel currentChannel) {}
+                public void onChannelChanged(Channel previousChannel, Channel currentChannel) {
+                    if (currentChannel != null) {
+                        GtvUtils.broadcastInputId(MainActivity.this, currentChannel.getInputId());
+                    }
+                }
             };
 
     private final Runnable mRestoreMainViewRunnable = this::restoreMainTvView;
diff --git a/src/com/android/tv/util/GtvUtils.java b/src/com/android/tv/util/GtvUtils.java
new file mode 100644
index 0000000..eb50e06
--- /dev/null
+++ b/src/com/android/tv/util/GtvUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tv.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+/** A utility class for Google TV */
+public class GtvUtils {
+    private static final String TAG = "GtvUtils";
+    private static final String AMATI_FEATURE = "com.google.android.feature.AMATI_EXPERIENCE";
+    private static final String PERMISSION_WRITE_EPG_DATA =
+            "com.android.providers.tv.permission.WRITE_EPG_DATA";
+    private static final String ACTION_INPUT_SELECTED = "android.apps.tv.launcherx.INPUT_SELECTED";
+    private static final String EXTRA_INPUT_ID = "extra_input_id";
+    private static final String LAUNCHERX_PACKAGE_NAME = "com.google.android.apps.tv.launcherx";
+    private static Boolean mEnabled = null;
+
+    private static boolean isEnabled(Context context) {
+        if (mEnabled == null) {
+            PackageManager pm = context.getPackageManager();
+            mEnabled = pm.hasSystemFeature(AMATI_FEATURE);
+        }
+        return mEnabled;
+    }
+
+    /** Broadcasts the intent with inputId to the Launcher */
+    public static void broadcastInputId(Context context, String inputId) {
+        if (isEnabled(context)) {
+            if (inputId == null) {
+                Log.e(TAG, "Will not broadcast inputId because it is null");
+            } else {
+                Intent intent = new Intent(ACTION_INPUT_SELECTED);
+                intent.putExtra(EXTRA_INPUT_ID, inputId);
+                intent.setPackage(LAUNCHERX_PACKAGE_NAME);
+                context.sendBroadcast(intent, PERMISSION_WRITE_EPG_DATA);
+            }
+        }
+    }
+}
diff --git a/tests/common/Android.mk b/tests/common/Android.mk
index b74a65d..7a232ff 100644
--- a/tests/common/Android.mk
+++ b/tests/common/Android.mk
@@ -17,6 +17,10 @@
     mockito-robolectric-prebuilt \
     tv-test-common \
 
+# Disable dexpreopt and <uses-library> check for test.
+LOCAL_ENFORCE_USES_LIBRARIES := false
+LOCAL_DEX_PREOPT := false
+
 LOCAL_INSTRUMENTATION_FOR := LiveTv
 
 LOCAL_MODULE_TAGS := optional
diff --git a/tests/input/Android.mk b/tests/input/Android.mk
index c15e4a4..4011dec 100644
--- a/tests/input/Android.mk
+++ b/tests/input/Android.mk
@@ -16,6 +16,10 @@
     tv-test-common \
     tv-common
 
+# Disable dexpreopt and <uses-library> check for test.
+LOCAL_ENFORCE_USES_LIBRARIES := false
+LOCAL_DEX_PREOPT := false
+
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res $(LOCAL_PATH)/res
 LOCAL_AAPT_FLAGS := --auto-add-overlay \
     --extra-packages com.android.tv.testing
diff --git a/tuner/sampletunertvinput/AndroidManifest.xml b/tuner/sampletunertvinput/AndroidManifest.xml
index d282889..8b25d0b 100644
--- a/tuner/sampletunertvinput/AndroidManifest.xml
+++ b/tuner/sampletunertvinput/AndroidManifest.xml
@@ -43,7 +43,8 @@
       android:theme="@android:style/Theme.Holo.Light.NoActionBar"
       android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
     <uses-library android:name="com.android.libraries.tv.tvsystem" android:required="false" />
-    <activity android:name=".SampleTunerTvInputSetupActivity" >
+    <activity android:name=".SampleTunerTvInputSetupActivity" 
+      android:exported="true">
       <intent-filter>
         <action android:name="android.intent.action.MAIN" />
       </intent-filter>
@@ -51,7 +52,8 @@
     <service android:name=".SampleTunerTvInputService"
         android:permission="android.permission.BIND_TV_INPUT"
         android:label="@string/sample_tuner_tv_input"
-        android:process="com.android.tv.samples.sampletunertvinput">
+        android:process="com.android.tv.samples.sampletunertvinput"
+        android:exported="true">
       <intent-filter>
         <action android:name="android.media.tv.TvInputService" />
       </intent-filter>
diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml
index 8fc96b2..909e243 100644
--- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml
+++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/AndroidManifest.xml
@@ -39,7 +39,8 @@
       android:icon="@mipmap/ic_launcher"
       android:theme="@android:style/Theme.Holo.Light.NoActionBar"
       android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
-    <activity android:name=".SampleTunerTvInputSetupActivity" >
+    <activity android:name=".SampleTunerTvInputSetupActivity"
+      android:exported="true">
       <intent-filter>
         <action android:name="android.intent.action.MAIN" />
       </intent-filter>
@@ -47,7 +48,8 @@
     <service android:name=".SampleTunerTvInputService"
         android:permission="android.permission.BIND_TV_INPUT"
         android:label="@string/sample_tuner_tv_input"
-        android:process="com.android.tv.samples.sampletunertvinput">
+        android:process="com.android.tv.samples.sampletunertvinput"
+        android:exported="true">
       <intent-filter>
         <action android:name="android.media.tv.TvInputService" />
       </intent-filter>
diff --git a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java
index 6ac9535..e86ced1 100644
--- a/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java
+++ b/tuner/sampletunertvinput/src/com/android/tv/samples/sampletunertvinput/SampleTunerTvInputService.java
@@ -1,13 +1,39 @@
 package com.android.tv.samples.sampletunertvinput;
 
+import static android.media.tv.TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN;
+
 import android.content.Context;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaFormat;
+import android.media.tv.tuner.dvr.DvrPlayback;
+import android.media.tv.tuner.dvr.DvrSettings;
+import android.media.tv.tuner.filter.AvSettings;
+import android.media.tv.tuner.filter.Filter;
+import android.media.tv.tuner.filter.FilterCallback;
+import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.filter.MediaEvent;
+import android.media.tv.tuner.filter.TsFilterConfiguration;
 import android.media.tv.tuner.frontend.AtscFrontendSettings;
+import android.media.tv.tuner.frontend.DvbtFrontendSettings;
 import android.media.tv.tuner.frontend.FrontendSettings;
+import android.media.tv.tuner.frontend.OnTuneEventListener;
 import android.media.tv.tuner.Tuner;
 import android.media.tv.TvInputService;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.ParcelFileDescriptor;
 import android.util.Log;
 import android.view.Surface;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
 
 
 /** SampleTunerTvInputService */
@@ -15,8 +41,39 @@
     private static final String TAG = "SampleTunerTvInput";
     private static final boolean DEBUG = true;
 
+    private static final int AUDIO_TPID = 257;
+    private static final int VIDEO_TPID = 256;
+    private static final int STATUS_MASK = 0xf;
+    private static final int LOW_THRESHOLD = 0x1000;
+    private static final int HIGH_THRESHOLD = 0x07fff;
+    private static final int FREQUENCY = 578000;
+    private static final int FILTER_BUFFER_SIZE = 16000000;
+    private static final int DVR_BUFFER_SIZE = 4000000;
+    private static final int INPUT_FILE_MAX_SIZE = 700000;
+    private static final int PACKET_SIZE = 188;
+
+    private static final int TIMEOUT_US = 100000;
+    private static final boolean SAVE_DATA = true;
+    private static final String ES_PATH = "/data/local/tmp/test.es";
+    private static final MediaFormat VIDEO_FORMAT;
+
+    static {
+        // format extracted for the specific input file
+        VIDEO_FORMAT = MediaFormat.createVideoFormat("video/avc", 320, 240);
+        VIDEO_FORMAT.setInteger(MediaFormat.KEY_TRACK_ID, 1);
+        VIDEO_FORMAT.setLong(MediaFormat.KEY_DURATION, 9933333);
+        VIDEO_FORMAT.setInteger(MediaFormat.KEY_LEVEL, 32);
+        VIDEO_FORMAT.setInteger(MediaFormat.KEY_PROFILE, 65536);
+        ByteBuffer csd = ByteBuffer.wrap(
+                new byte[] {0, 0, 0, 1, 103, 66, -64, 20, -38, 5, 7, -24, 64, 0, 0, 3, 0, 64, 0,
+                        0, 15, 35, -59, 10, -88});
+        VIDEO_FORMAT.setByteBuffer("csd-0", csd);
+        csd = ByteBuffer.wrap(new byte[] {0, 0, 0, 1, 104, -50, 60, -128});
+        VIDEO_FORMAT.setByteBuffer("csd-1", csd);
+    }
+
     public static final String INPUT_ID =
-        "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService";
+            "com.android.tv.samples.sampletunertvinput/.SampleTunerTvInputService";
     private String mSessionId;
 
     @Override
@@ -36,9 +93,19 @@
 
     class TvInputSessionImpl extends Session {
 
-        private Surface surface;
         private final Context mContext;
-        Tuner tuner;
+        private Handler mHandler;
+
+        private Surface mSurface;
+        private Filter mAudioFilter;
+        private Filter mVideoFilter;
+        private DvrPlayback mDvr;
+        private Tuner mTuner;
+        private MediaCodec mMediaCodec;
+        private Thread mDecoderThread;
+        private Deque<MediaEvent> mDataQueue;
+        private List<MediaEvent> mSavedData;
+        private boolean mDataReady = false;
 
 
         public TvInputSessionImpl(Context context) {
@@ -51,6 +118,28 @@
             if (DEBUG) {
                 Log.d(TAG, "onRelease");
             }
+            if (mDecoderThread != null) {
+                mDecoderThread.interrupt();
+                mDecoderThread = null;
+            }
+            if (mMediaCodec != null) {
+                mMediaCodec.release();
+                mMediaCodec = null;
+            }
+            if (mAudioFilter != null) {
+                mAudioFilter.close();
+            }
+            if (mVideoFilter != null) {
+                mVideoFilter.close();
+            }
+            if (mDvr != null) {
+                mDvr.close();
+            }
+            if (mTuner != null) {
+                mTuner.close();
+            }
+            mDataQueue = null;
+            mSavedData = null;
         }
 
         @Override
@@ -58,7 +147,7 @@
             if (DEBUG) {
                 Log.d(TAG, "onSetSurface");
             }
-            this.surface = surface;
+            this.mSurface = surface;
             return true;
         }
 
@@ -74,20 +163,16 @@
             if (DEBUG) {
                 Log.d(TAG, "onTune " + uri);
             }
-            tuner = new Tuner(mContext, mSessionId,
-                    TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
-
-            int feCount = tuner.getFrontendIds().size();
-            if (feCount <= 0) return false;
-
-            AtscFrontendSettings settings =
-                    AtscFrontendSettings
-                            .builder()
-                            .setFrequency(2000)
-                            .setModulation(AtscFrontendSettings.MODULATION_AUTO)
-                            .build();
-            tuner.tune(settings);
-
+            if (!initCodec()) {
+                Log.e(TAG, "null codec!");
+                return false;
+            }
+            mHandler = new Handler();
+            mDecoderThread =
+                    new Thread(
+                            this::decodeInternal,
+                            "sample-tuner-tis-decoder-thread");
+            mDecoderThread.start();
             return true;
         }
 
@@ -97,5 +182,269 @@
                 Log.d(TAG, "onSetCaptionEnabled " + b);
             }
         }
+
+        private Filter audioFilter() {
+            Filter audioFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_AUDIO,
+                    FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler),
+                    new FilterCallback() {
+                        @Override
+                        public void onFilterEvent(Filter filter, FilterEvent[] events) {
+                            if (DEBUG) {
+                                Log.d(TAG, "onFilterEvent audio, size=" + events.length);
+                            }
+                            for (int i = 0; i < events.length; i++) {
+                                if (DEBUG) {
+                                    Log.d(TAG, "events[" + i + "] is "
+                                            + events[i].getClass().getSimpleName());
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onFilterStatusChanged(Filter filter, int status) {
+                            if (DEBUG) {
+                                Log.d(TAG, "onFilterEvent audio, status=" + status);
+                            }
+                        }
+                    });
+            AvSettings settings =
+                    AvSettings.builder(Filter.TYPE_TS, true).setPassthrough(false).build();
+            audioFilter.configure(
+                    TsFilterConfiguration.builder().setTpid(AUDIO_TPID)
+                            .setSettings(settings).build());
+            return audioFilter;
+        }
+
+        private Filter videoFilter() {
+            Filter videoFilter = mTuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_VIDEO,
+                    FILTER_BUFFER_SIZE, new HandlerExecutor(mHandler),
+                    new FilterCallback() {
+                        @Override
+                        public void onFilterEvent(Filter filter, FilterEvent[] events) {
+                            if (DEBUG) {
+                                Log.d(TAG, "onFilterEvent video, size=" + events.length);
+                            }
+                            for (int i = 0; i < events.length; i++) {
+                                if (DEBUG) {
+                                    Log.d(TAG, "events[" + i + "] is "
+                                            + events[i].getClass().getSimpleName());
+                                }
+                                if (events[i] instanceof MediaEvent) {
+                                    MediaEvent me = (MediaEvent) events[i];
+                                    mDataQueue.add(me);
+                                    if (SAVE_DATA) {
+                                        mSavedData.add(me);
+                                    }
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onFilterStatusChanged(Filter filter, int status) {
+                            if (DEBUG) {
+                                Log.d(TAG, "onFilterEvent video, status=" + status);
+                                if (status == Filter.STATUS_DATA_READY) {
+                                    mDataReady = true;
+                                }
+                            }
+                        }
+                    });
+            AvSettings settings =
+                    AvSettings.builder(Filter.TYPE_TS, false).setPassthrough(false).build();
+            videoFilter.configure(
+                    TsFilterConfiguration.builder().setTpid(VIDEO_TPID)
+                            .setSettings(settings).build());
+            return videoFilter;
+        }
+
+        private DvrPlayback dvrPlayback() {
+            DvrPlayback dvr = mTuner.openDvrPlayback(DVR_BUFFER_SIZE, new HandlerExecutor(mHandler),
+                    status -> {
+                        if (DEBUG) {
+                            Log.d(TAG, "onPlaybackStatusChanged status=" + status);
+                        }
+                    });
+            int res = dvr.configure(
+                    DvrSettings.builder()
+                            .setStatusMask(STATUS_MASK)
+                            .setLowThreshold(LOW_THRESHOLD)
+                            .setHighThreshold(HIGH_THRESHOLD)
+                            .setDataFormat(DvrSettings.DATA_FORMAT_ES)
+                            .setPacketSize(PACKET_SIZE)
+                            .build());
+            if (DEBUG) {
+                Log.d(TAG, "config res=" + res);
+            }
+            File file = new File(ES_PATH);
+            if (file.exists()) {
+                try {
+                    dvr.setFileDescriptor(
+                            ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE));
+                } catch (FileNotFoundException e) {
+                        Log.e(TAG, "Failed to create FD");
+                }
+            } else {
+                Log.w(TAG, "File not existing");
+            }
+            return dvr;
+        }
+
+        private void tune() {
+            DvbtFrontendSettings feSettings = DvbtFrontendSettings.builder()
+                    .setFrequency(FREQUENCY)
+                    .setTransmissionMode(DvbtFrontendSettings.TRANSMISSION_MODE_AUTO)
+                    .setBandwidth(DvbtFrontendSettings.BANDWIDTH_8MHZ)
+                    .setConstellation(DvbtFrontendSettings.CONSTELLATION_AUTO)
+                    .setHierarchy(DvbtFrontendSettings.HIERARCHY_AUTO)
+                    .setHighPriorityCodeRate(DvbtFrontendSettings.CODERATE_AUTO)
+                    .setLowPriorityCodeRate(DvbtFrontendSettings.CODERATE_AUTO)
+                    .setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_AUTO)
+                    .setHighPriority(true)
+                    .setStandard(DvbtFrontendSettings.STANDARD_T)
+                    .build();
+            mTuner.setOnTuneEventListener(new HandlerExecutor(mHandler), new OnTuneEventListener() {
+                @Override
+                public void onTuneEvent(int tuneEvent) {
+                    if (DEBUG) {
+                        Log.d(TAG, "onTuneEvent " + tuneEvent);
+                    }
+                    long read = mDvr.read(INPUT_FILE_MAX_SIZE);
+                    if (DEBUG) {
+                        Log.d(TAG, "read=" + read);
+                    }
+                }
+            });
+            mTuner.tune(feSettings);
+        }
+
+        private boolean initCodec() {
+            if (mMediaCodec != null) {
+                mMediaCodec.release();
+                mMediaCodec = null;
+            }
+            try {
+                mMediaCodec = MediaCodec.createDecoderByType("video/avc");
+                mMediaCodec.configure(VIDEO_FORMAT, mSurface, null, 0);
+            } catch (IOException e) {
+                Log.e(TAG, "Error: " + e.getMessage());
+            }
+
+            if (mMediaCodec == null) {
+                Log.e(TAG, "null codec!");
+                notifyVideoUnavailable(VIDEO_UNAVAILABLE_REASON_UNKNOWN);
+                return false;
+            }
+            return true;
+        }
+
+        private void decodeInternal() {
+            mDataQueue = new ArrayDeque<>();
+            mSavedData = new ArrayList<>();
+            mTuner = new Tuner(mContext, mSessionId,
+                    TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+
+            mAudioFilter = audioFilter();
+            mVideoFilter = videoFilter();
+            mAudioFilter.start();
+            mVideoFilter.start();
+            // use dvr playback to feed the data on platform without physical tuner
+            mDvr = dvrPlayback();
+            tune();
+            mDvr.start();
+            mMediaCodec.start();
+
+            try {
+                while (!Thread.interrupted()) {
+                    if (!mDataReady) {
+                        Thread.sleep(100);
+                    }
+                    if (!mDataQueue.isEmpty()) {
+                        if (queueCodecInputBuffer(mDataQueue.getFirst())) {
+                            // data consumed, remove.
+                            mDataQueue.pollFirst();
+                        }
+                    } else if (SAVE_DATA) {
+                        mDataQueue.addAll(mSavedData);
+                    }
+                    releaseCodecOutputBuffer();
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Error: " + e.getMessage());
+            }
+        }
+
+        private boolean queueCodecInputBuffer(MediaEvent mediaEvent) {
+            int res = mMediaCodec.dequeueInputBuffer(TIMEOUT_US);
+            if (res >= 0) {
+                ByteBuffer buffer = mMediaCodec.getInputBuffer(res);
+                if (buffer == null) {
+                    throw new RuntimeException("Null decoder input buffer");
+                }
+
+                ByteBuffer data = mediaEvent.getLinearBlock().map();
+                int sampleSize = (int) mediaEvent.getDataLength();
+                int offset = (int) mediaEvent.getOffset();
+                long pts = mediaEvent.getPts();
+
+                if (offset > 0 && offset < data.limit()) {
+                    data.position(offset);
+                } else {
+                    data.position(0);
+                }
+
+                if (DEBUG) {
+                    Log.d(
+                        TAG,
+                        "Decoder: Send data to decoder."
+                            + " Sample size="
+                            + sampleSize
+                            + " pts="
+                            + pts
+                            + " limit="
+                            + data.limit()
+                            + " pos="
+                            + data.position()
+                            + " size="
+                            + (data.limit() - data.position()));
+                }
+                while (data.position() < data.limit()) {
+                    // fill codec input buffer
+                    buffer.put(data.get());
+                }
+
+                mMediaCodec.queueInputBuffer(res, 0, sampleSize, pts, 0);
+            } else {
+                Log.d(TAG, "queueCodecInputBuffer res=" + res);
+                return false;
+            }
+            return true;
+        }
+
+        private void releaseCodecOutputBuffer() {
+            // play frames
+            BufferInfo bufferInfo = new BufferInfo();
+            int res = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
+            if (res >= 0) {
+                mMediaCodec.releaseOutputBuffer(res, true);
+                notifyVideoAvailable();
+                if (DEBUG) {
+                    Log.d(TAG, "notifyVideoAvailable");
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat format = mMediaCodec.getOutputFormat();
+                if (DEBUG) {
+                    Log.d(TAG, "releaseCodecOutputBuffer: Output format changed:" + format);
+                }
+            } else if (res == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                if (DEBUG) {
+                    Log.d(TAG, "releaseCodecOutputBuffer: timeout");
+                }
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "Return value of releaseCodecOutputBuffer:" + res);
+                }
+            }
+        }
+
     }
 }
\ No newline at end of file