Snap for 8570526 from 92f369e880de8a5c063869e790547911a767a84b to mainline-mediaprovider-release

Change-Id: Iffd201222e17d882241ddbaae7137d0533134572
diff --git a/Android.bp b/Android.bp
index 79ae05c..951d6b0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,6 +14,23 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["packages_apps_TV_license"],
+}
+
+// See: http://go/android-license-faq
+license {
+    name: "packages_apps_TV_license",
+    package_name: "Android Live TV App",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+        "SPDX-license-identifier-BSD",
+        "SPDX-license-identifier-MIT",
+    ],
+    license_text: ["res/raw/third_party_licenses"],
+}
+
 version_name = "1.24-asop"
 version_code = "417000452"
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0cbc55d..75e2c4d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -15,296 +15,270 @@
   ~ limitations under the License.
 -->
 <!-- This manifest is for LiveTv -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.tv" >
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.tv">
 
-    <uses-sdk
-        android:minSdkVersion="23"
-        android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="29"/>
 
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.HDMI_CEC" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MODIFY_PARENTAL_CONTROLS" />
-    <uses-permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS" />
-    <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.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" />
-    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
-    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.HDMI_CEC"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MODIFY_PARENTAL_CONTROLS"/>
+    <uses-permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
+    <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"/>
+    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
+    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/>
     <!-- Permissions/feature for USB tuner -->
-    <uses-permission android:name="android.permission.DVB_DEVICE" />
+    <uses-permission android:name="android.permission.DVB_DEVICE"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
 
-    <uses-feature
-        android:name="android.hardware.usb.host"
-        android:required="false" />
+    <uses-feature android:name="android.hardware.usb.host"
+         android:required="false"/>
 
     <!-- Limit only for Android TV -->
-    <uses-feature
-        android:name="android.software.leanback"
-        android:required="true" />
-    <uses-feature
-        android:name="android.software.live_tv"
-        android:required="true" />
-    <uses-feature
-        android:name="android.hardware.touchscreen"
-        android:required="false" />
+    <uses-feature android:name="android.software.leanback"
+         android:required="true"/>
+    <uses-feature android:name="android.software.live_tv"
+         android:required="true"/>
+    <uses-feature android:name="android.hardware.touchscreen"
+         android:required="false"/>
 
     <!-- Receives input events from the TV app. -->
-    <permission
-        android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT"
-        android:description="@string/permdesc_receiveInputEvent"
-        android:label="@string/permlab_receiveInputEvent"
-        android:protectionLevel="signatureOrSystem" />
+    <permission android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT"
+         android:description="@string/permdesc_receiveInputEvent"
+         android:label="@string/permlab_receiveInputEvent"
+         android:protectionLevel="signatureOrSystem"/>
     <!-- Customizes Live TV with customization packages. -->
-    <permission
-        android:name="com.android.tv.permission.CUSTOMIZE_TV_APP"
-        android:description="@string/permdesc_customizeTvApp"
-        android:label="@string/permlab_customizeTvApp"
-        android:protectionLevel="signatureOrSystem" />
+    <permission android:name="com.android.tv.permission.CUSTOMIZE_TV_APP"
+         android:description="@string/permdesc_customizeTvApp"
+         android:label="@string/permlab_customizeTvApp"
+         android:protectionLevel="signatureOrSystem"/>
 
-    <application
-        android:name="com.android.tv.app.LiveTvApplication"
-        android:allowBackup="true"
-        android:appComponentFactory="android.support.v4.app.CoreComponentFactory"
-        android:banner="@drawable/live_tv_banner"
-        android:icon="@drawable/ic_tv_app"
-        android:label="@string/app_name"
-        android:supportsRtl="true"
-        android:theme="@style/Theme.TV"
-        tools:replace="android:appComponentFactory" >
+    <application android:name="com.android.tv.app.LiveTvApplication"
+         android:allowBackup="true"
+         android:appComponentFactory="android.support.v4.app.CoreComponentFactory"
+         android:banner="@drawable/live_tv_banner"
+         android:icon="@drawable/ic_tv_app"
+         android:label="@string/app_name"
+         android:supportsRtl="true"
+         android:theme="@style/Theme.TV"
+         tools:replace="android:appComponentFactory">
 
         <!-- providers are listed here to keep them separate from the internal versions -->
-        <provider
-            android:name="com.android.tv.search.LocalSearchProvider"
-            android:authorities="com.android.tv.search"
-            android:enabled="true"
-            android:exported="true" >
-            <meta-data
-                android:name="SupportedSwitchActionType"
-                android:value="CHANNEL|TVINPUT" />
+        <provider android:name="com.android.tv.search.LocalSearchProvider"
+             android:authorities="com.android.tv.search"
+             android:enabled="true"
+             android:exported="true">
+            <meta-data android:name="SupportedSwitchActionType"
+                 android:value="CHANNEL|TVINPUT"/>
         </provider>
-        <provider
-            android:name="com.android.tv.common.CommonPreferenceProvider"
-            android:authorities="com.android.tv.common.preferences"
-            android:exported="false"
-            android:process="com.android.tv.common" />
+        <provider android:name="com.android.tv.common.CommonPreferenceProvider"
+             android:authorities="com.android.tv.common.preferences"
+             android:exported="false"
+             android:process="com.android.tv.common"/>
 
 
 
-        <receiver
-            android:name="com.android.tv.livetv.receiver.GlobalKeyReceiver"
-            android:exported="true" >
+        <receiver android:name="com.android.tv.livetv.receiver.GlobalKeyReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.GLOBAL_BUTTON" />
+                <action android:name="android.intent.action.GLOBAL_BUTTON"/>
             </intent-filter>
 
             <!--
-             Not directly related to GlobalKeyReceiver but needed to be able to provide our
-            content rating definitions to the system service.
-            -->
+                             Not directly related to GlobalKeyReceiver but needed to be able to provide our
+                            content rating definitions to the system service.
+                            -->
             <intent-filter>
-                <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
+                <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
-                android:resource="@xml/tv_content_rating_systems" />
+            <meta-data android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
+                 android:resource="@xml/tv_content_rating_systems"/>
         </receiver>
 
-        <activity
-            android:name="com.android.tv.TvActivity"
-            android:exported="true"
-            android:launchMode="singleTask" >
+        <activity android:name="com.android.tv.TvActivity"
+             android:exported="true"
+             android:launchMode="singleTask">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
 
-                <category android:name="android.intent.category.LAUNCHER" />
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
-            </intent-filter>
-        </activity>
-        <activity
-            android:name="com.android.tv.MainActivity"
-            android:configChanges="keyboard|keyboardHidden|screenSize|smallestScreenSize|screenLayout|orientation"
-            android:launchMode="singleTask"
-            android:resizeableActivity="true"
-            android:screenOrientation="landscape"
-            android:supportsPictureInPicture="true"
-            android:theme="@style/Theme.TV.MainActivity" >
-            <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-
-                <data android:mimeType="vnd.android.cursor.item/channel" />
-                <data android:mimeType="vnd.android.cursor.dir/channel" />
-                <data android:mimeType="vnd.android.cursor.item/program" />
-                <data android:mimeType="vnd.android.cursor.dir/program" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.media.tv.action.SETUP_INPUTS" />
-
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.SEARCH" />
-            </intent-filter>
-
-            <meta-data
-                android:name="supports_leanback"
-                android:value="true" />
-            <meta-data
-                android:name="android.app.searchable"
-                android:resource="@xml/searchable" />
-        </activity>
-        <activity
-            android:name="com.android.tv.LauncherActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
-        <activity
-            android:name="com.android.tv.SetupPassthroughActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:exported="true"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" >
-            <intent-filter>
-                <action android:name="com.android.tv.action.LAUNCH_INPUT_SETUP" />
-
+                <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.SelectInputActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:launchMode="singleTask"
-            android:theme="@style/Theme.SelectInputActivity" />
-        <activity
-            android:name="com.android.tv.onboarding.OnboardingActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:launchMode="singleTop"
-            android:theme="@style/Theme.Setup.GuidedStep" />
-        <activity
-            android:name="com.android.tv.dvr.ui.browse.DvrBrowseActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:launchMode="singleTask"
-            android:theme="@style/Theme.Leanback.Browse" >
+        <activity android:name="com.android.tv.MainActivity"
+             android:configChanges="keyboard|keyboardHidden|screenSize|smallestScreenSize|screenLayout|orientation"
+             android:launchMode="singleTask"
+             android:resizeableActivity="true"
+             android:screenOrientation="landscape"
+             android:supportsPictureInPicture="true"
+             android:theme="@style/Theme.TV.MainActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.action.VIEW_RECORDING_SCHEDULES" />
+                <action android:name="android.intent.action.VIEW"/>
 
-                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.DEFAULT"/>
+
+                <data android:mimeType="vnd.android.cursor.item/channel"/>
+                <data android:mimeType="vnd.android.cursor.dir/channel"/>
+                <data android:mimeType="vnd.android.cursor.item/program"/>
+                <data android:mimeType="vnd.android.cursor.dir/program"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
+                <action android:name="android.media.tv.action.SETUP_INPUTS"/>
 
-                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH"/>
+            </intent-filter>
 
-                <data android:mimeType="vnd.android.cursor.dir/recorded_program" />
+            <meta-data android:name="supports_leanback"
+                 android:value="true"/>
+            <meta-data android:name="android.app.searchable"
+                 android:resource="@xml/searchable"/>
+        </activity>
+        <activity android:name="com.android.tv.LauncherActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
+        <activity android:name="com.android.tv.SetupPassthroughActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:exported="true"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar">
+            <intent-filter>
+                <action android:name="com.android.tv.action.LAUNCH_INPUT_SETUP"/>
+
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
-        <activity
-            android:name="com.android.tv.dvr.ui.playback.DvrPlaybackActivity"
-            android:configChanges="keyboard|keyboardHidden|screenSize|smallestScreenSize|screenLayout|orientation"
-            android:launchMode="singleTask"
-            android:theme="@style/Theme.Leanback" >
+        <activity android:name="com.android.tv.SelectInputActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:launchMode="singleTask"
+             android:theme="@style/Theme.SelectInputActivity">
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-
+                <action android:name="com.android.tv.action.VIEW_INPUTS" />
                 <category android:name="android.intent.category.DEFAULT" />
-
-                <data android:mimeType="vnd.android.cursor.item/recorded_program" />
             </intent-filter>
         </activity>
-        <activity
-            android:name="com.android.tv.ui.DetailsActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:exported="true"
-            android:theme="@style/Theme.TV.Dvr.Browse.Details" />
-        <activity
-            android:name="com.android.tv.dvr.ui.DvrRecordingSettingsActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:exported="false"
-            android:theme="@style/Theme.TV.Dvr.Series.Settings.GuidedStep" />
-        <activity
-            android:name="com.android.tv.dvr.ui.DvrSeriesSettingsActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@style/Theme.TV.Dvr.Series.Settings.GuidedStep" />
-        <activity
-            android:name="com.android.tv.dvr.ui.DvrSeriesDeletionActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@style/Theme.TV.Dvr.Series.Deletion.GuidedStep" />
-        <activity
-            android:name="com.android.tv.dvr.ui.DvrSeriesScheduledDialogActivity"
-            android:theme="@style/Theme.TV.dialog.HalfSizedDialog" />
-        <activity
-            android:name="com.android.tv.dvr.ui.list.DvrSchedulesActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@style/Theme.Leanback.Details" />
-        <activity
-            android:name="com.android.tv.dvr.ui.list.DvrHistoryActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:exported="false"
-            android:theme="@style/Theme.Leanback.Details" />
-
-        <service
-            android:name="com.android.tv.recommendation.NotificationService"
-            android:exported="false" />
-        <service
-            android:name="com.android.tv.recommendation.ChannelPreviewUpdater$ChannelPreviewUpdateService"
-            android:permission="android.permission.BIND_JOB_SERVICE" />
-
-        <receiver
-            android:name="com.android.tv.receiver.BootCompletedReceiver"
-            android:exported="true" >
+        <activity android:name="com.android.tv.onboarding.OnboardingActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:launchMode="singleTop"
+             android:theme="@style/Theme.Setup.GuidedStep"/>
+        <activity android:name="com.android.tv.dvr.ui.browse.DvrBrowseActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:launchMode="singleTask"
+             android:theme="@style/Theme.Leanback.Browse"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.media.tv.action.VIEW_RECORDING_SCHEDULES"/>
+
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+
+                <category android:name="android.intent.category.DEFAULT"/>
+
+                <data android:mimeType="vnd.android.cursor.dir/recorded_program"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.android.tv.dvr.ui.playback.DvrPlaybackActivity"
+             android:configChanges="keyboard|keyboardHidden|screenSize|smallestScreenSize|screenLayout|orientation"
+             android:launchMode="singleTask"
+             android:theme="@style/Theme.Leanback"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+
+                <category android:name="android.intent.category.DEFAULT"/>
+
+                <data android:mimeType="vnd.android.cursor.item/recorded_program"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="com.android.tv.ui.DetailsActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:exported="true"
+             android:theme="@style/Theme.TV.Dvr.Browse.Details"/>
+        <activity android:name="com.android.tv.dvr.ui.DvrRecordingSettingsActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:exported="false"
+             android:theme="@style/Theme.TV.Dvr.Series.Settings.GuidedStep"/>
+        <activity android:name="com.android.tv.dvr.ui.DvrSeriesSettingsActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:theme="@style/Theme.TV.Dvr.Series.Settings.GuidedStep"/>
+        <activity android:name="com.android.tv.dvr.ui.DvrSeriesDeletionActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:theme="@style/Theme.TV.Dvr.Series.Deletion.GuidedStep"/>
+        <activity android:name="com.android.tv.dvr.ui.DvrSeriesScheduledDialogActivity"
+             android:theme="@style/Theme.TV.dialog.HalfSizedDialog"/>
+        <activity android:name="com.android.tv.dvr.ui.list.DvrSchedulesActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:theme="@style/Theme.Leanback.Details"/>
+        <activity android:name="com.android.tv.dvr.ui.list.DvrHistoryActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:exported="false"
+             android:theme="@style/Theme.Leanback.Details"/>
+
+        <service android:name="com.android.tv.recommendation.NotificationService"
+             android:exported="false"/>
+        <service android:name="com.android.tv.recommendation.ChannelPreviewUpdater$ChannelPreviewUpdateService"
+             android:permission="android.permission.BIND_JOB_SERVICE"/>
+
+        <receiver android:name="com.android.tv.receiver.BootCompletedReceiver"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
-        <receiver
-            android:name="com.android.tv.receiver.PackageIntentsReceiver"
-            android:exported="true" >
+        <receiver android:name="com.android.tv.receiver.PackageIntentsReceiver"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.PACKAGE_ADDED" />
+                <action android:name="android.intent.action.PACKAGE_ADDED"/>
                 <!-- PACKAGE_CHANGED for package enabled/disabled notification -->
-                <action android:name="android.intent.action.PACKAGE_CHANGED" />
-                <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
-                <action android:name="android.intent.action.PACKAGE_REMOVED" />
+                <action android:name="android.intent.action.PACKAGE_CHANGED"/>
+                <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED"/>
+                <action android:name="android.intent.action.PACKAGE_REMOVED"/>
 
-                <data android:scheme="package" />
+                <data android:scheme="package"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
         </receiver> <!-- System initial setup component definition -->
-        <activity
-            android:name="com.android.tv.setup.SystemSetupActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:exported="true"
-            android:label="@string/app_name"
-            android:launchMode="singleInstance"
-            android:theme="@style/Theme.Setup.GuidedStep" >
+        <activity android:name="com.android.tv.setup.SystemSetupActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:exported="true"
+             android:label="@string/app_name"
+             android:launchMode="singleInstance"
+             android:theme="@style/Theme.Setup.GuidedStep">
             <intent-filter>
-                <action android:name="com.android.tv.action.LAUNCH_SYSTEM_SETUP" />
+                <action android:name="com.android.tv.action.LAUNCH_SYSTEM_SETUP"/>
 
-                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity> <!-- DVR -->
-        <service
-            android:name="com.android.tv.dvr.recorder.DvrRecordingService"
-            android:label="@string/dvr_service_name" />
+        <service android:name="com.android.tv.dvr.recorder.DvrRecordingService"
+             android:label="@string/dvr_service_name"/>
 
-        <receiver
-            android:name="com.android.tv.dvr.recorder.DvrStartRecordingReceiver"
-            android:exported="false" />
+        <receiver android:name="com.android.tv.dvr.recorder.DvrStartRecordingReceiver"
+             android:exported="false"/>
 
-        <service
-            android:name="com.android.tv.data.epg.EpgFetchService"
-            android:permission="android.permission.BIND_JOB_SERVICE" />
+        <service android:name="com.android.tv.data.epg.EpgFetchService"
+             android:permission="android.permission.BIND_JOB_SERVICE"/>
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/OWNERS b/OWNERS
index 4aa5fe5..e904f5c 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,3 @@
 nchalko@google.com
 shubang@google.com
+quxiangfang@google.com
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/common/Android.bp b/common/Android.bp
index 728587f..f6ba940 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_library {
     name: "tv-common",
     srcs: [
diff --git a/common/lint-baseline.xml b/common/lint-baseline.xml
new file mode 100644
index 0000000..b6f9dfc
--- /dev/null
+++ b/common/lint-baseline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 26 (current min is 23): `android.os.StrictMode.VmPolicy.Builder#detectContentUriWithoutPermission`"
+        errorLine1="                            .detectContentUriWithoutPermission()"
+        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/common/src/com/android/tv/common/BaseApplication.java"
+            line="84"
+            column="30"/>
+    </issue>
+
+</issues>
diff --git a/common/src/com/android/tv/common/CommonPreferences.java b/common/src/com/android/tv/common/CommonPreferences.java
index 5a94eec..72acfd1 100644
--- a/common/src/com/android/tv/common/CommonPreferences.java
+++ b/common/src/com/android/tv/common/CommonPreferences.java
@@ -164,7 +164,8 @@
         }
     }
 
-    public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) {
+    @TrickplaySetting
+    public static synchronized int getTrickplaySetting(Context context) {
         SoftPreconditions.checkState(sInitialized);
         if (useContentProvider(context)) {
             return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
diff --git a/common/src/com/android/tv/common/flags/SetupFlags.java b/common/src/com/android/tv/common/flags/SetupFlags.java
index 0a7f200..e5901e4 100755
--- a/common/src/com/android/tv/common/flags/SetupFlags.java
+++ b/common/src/com/android/tv/common/flags/SetupFlags.java
@@ -29,8 +29,8 @@
 
     /** Packages allowed to send intents to SetupPassthroughActivity. */
     com.android.tv.common.flags.proto.TypedFeatures.StringListParam
-            setupPassThroughPackageWhitelist();
+            setupPassThroughPackageAllowlist();
 
-    /** Use a whitelist for packages allowed to start SetupPassthroughActivity */
-    boolean useWhitelistForSetupPassThrough();
+    /** Use a allowlist for packages allowed to start SetupPassthroughActivity */
+    boolean useAllowlistForSetupPassThrough();
 }
diff --git a/common/src/com/android/tv/common/flags/impl/DefaultSetupFlags.java b/common/src/com/android/tv/common/flags/impl/DefaultSetupFlags.java
index 3abe662..a778068 100644
--- a/common/src/com/android/tv/common/flags/impl/DefaultSetupFlags.java
+++ b/common/src/com/android/tv/common/flags/impl/DefaultSetupFlags.java
@@ -27,12 +27,12 @@
     }
 
     @Override
-    public StringListParam setupPassThroughPackageWhitelist() {
+    public StringListParam setupPassThroughPackageAllowlist() {
         return StringListParam.getDefaultInstance();
     }
 
     @Override
-    public boolean useWhitelistForSetupPassThrough() {
+    public boolean useAllowlistForSetupPassThrough() {
         return false;
     }
 }
diff --git a/common/tests/robotests/Android.mk b/common/tests/robotests/Android.mk
index 0452878..85512fb 100644
--- a/common/tests/robotests/Android.mk
+++ b/common/tests/robotests/Android.mk
@@ -5,6 +5,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := TvCommonRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -34,6 +36,8 @@
 #############################################################
 include $(CLEAR_VARS)
 LOCAL_MODULE := RunTvCommonRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_ROBOTEST_FILES := $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.)
 
diff --git a/jni/Android.bp b/jni/Android.bp
index bbf2778..596cb5d 100644
--- a/jni/Android.bp
+++ b/jni/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_shared {
     name: "libtunertvinput_jni",
     srcs: [
@@ -26,5 +30,6 @@
     ],
     sdk_version: "23",
     stl: "c++_static",
+    header_libs: ["jni_headers"],
     shared_libs: ["liblog"],
 }
diff --git a/jni/minijail/Android.bp b/jni/minijail/Android.bp
new file mode 100644
index 0000000..cb92115
--- /dev/null
+++ b/jni/minijail/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2017 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "libminijail_jni",
+    srcs: ["minijail.cpp"],
+    stl: "none",
+    header_libs: ["jni_headers"],
+    static_libs: [
+        "libc++_static",
+        "libminijail",
+    ],
+    shared_libs: ["liblog"],
+}
diff --git a/jni/minijail/Android.mk b/jni/minijail/Android.mk
deleted file mode 100644
index 7a3ad58..0000000
--- a/jni/minijail/Android.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-# --------------------------------------------------------------
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libminijail_jni
-LOCAL_SRC_FILES := minijail.cpp
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_CXX_STL := none
-LOCAL_STATIC_LIBRARIES := libc++_static libminijail
-LOCAL_LDLIBS := -llog
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/Android.bp b/libs/Android.bp
index 6c768fc..ae5e1a9 100644
--- a/libs/Android.bp
+++ b/libs/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_import {
     name: "tv-auto-common-jar",
     jars: ["m2/auto-common-0.10.jar"],
diff --git a/lint-baseline.xml b/lint-baseline.xml
new file mode 100644
index 0000000..d91a189
--- /dev/null
+++ b/lint-baseline.xml
@@ -0,0 +1,422 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 24 (current min is 23): `android.media.tv.TvContract.RecordedPrograms#CONTENT_URI`"
+        errorLine1="                        context, TvContract.RecordedPrograms.CONTENT_URI)) {"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/AsyncDbTask.java"
+            line="137"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 24 (current min is 23): `android.media.tv.TvContract.RecordedPrograms#CONTENT_URI`"
+        errorLine1="                        context, TvContract.RecordedPrograms.CONTENT_URI)) {"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/AsyncDbTask.java"
+            line="143"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `updateAndStartServiceIfNeeded`"
+        errorLine1="            scheduler.updateAndStartServiceIfNeeded();"
+        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/receiver/BootCompletedReceiver.java"
+            line="90"
+            column="23"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="        if (!TvContract.isChannelUriForPassthroughInput(uri)) {"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/data/ChannelImpl.java"
+            line="444"
+            column="25"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#canRecord`"
+        errorLine1="                if (info.canRecord()) {"
+        errorLine2="                         ~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/menu/ChannelsRowAdapter.java"
+            line="255"
+            column="26"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.session.MediaController.TransportControls#prepare`"
+        errorLine1="        getActivity().getMediaController().getTransportControls().prepare();"
+        errorLine2="                                                                  ~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/dvr/ui/playback/DvrPlaybackOverlayFragment.java"
+            line="448"
+            column="67"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvView#timeShiftPlay`"
+        errorLine1="            mTvView.timeShiftPlay(mInputId, mRecordedProgramUri);"
+        errorLine2="                    ~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/dvr/DvrTvView.java"
+            line="77"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#loadCustomLabel`"
+        errorLine1="        CharSequence customLabel = input.loadCustomLabel(getContext());"
+        errorLine2="                                         ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/ui/InputBannerView.java"
+            line="75"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#canRecord`"
+        errorLine1="            tunerCount = mInput.canRecord() ? mInput.getTunerCount() : 0;"
+        errorLine2="                                ~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/dvr/recorder/InputTaskScheduler.java"
+            line="310"
+            column="33"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#getTunerCount`"
+        errorLine1="            tunerCount = mInput.canRecord() ? mInput.getTunerCount() : 0;"
+        errorLine2="                                                     ~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/dvr/recorder/InputTaskScheduler.java"
+            line="310"
+            column="54"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="                TvContract.isChannelUriForPassthroughInput(getIntent().getData());"
+        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/MainActivity.java"
+            line="534"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="            if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {"
+        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/MainActivity.java"
+            line="1002"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="        if ((channelUri == null || !TvContract.isChannelUriForPassthroughInput(channelUri))"
+        errorLine2="                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/MainActivity.java"
+            line="1029"
+            column="48"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="                TvContract.isChannelUriForPassthroughInput(channelUri)"
+        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/MainActivity.java"
+            line="1037"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="            if (TvContract.isChannelUriForPassthroughInput(channelUri)) {"
+        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/MainActivity.java"
+            line="1065"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="            } else if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {"
+        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/MainActivity.java"
+            line="1544"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Method reference requires API level 24 (current min is 23): `MainActivity.super::enterPictureInPictureMode`"
+        errorLine1="            mHandler.post(MainActivity.super::enterPictureInPictureMode);"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/MainActivity.java"
+            line="2402"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="        return TvContract.isChannelUriForPassthroughInput(uri)"
+        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/MainActivity.java"
+            line="2813"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 28 (current min is 23): `android.media.tv.TvInputManager#getBlockedRatings`"
+        errorLine1="            for (TvContentRating tvContentRating : mTvInputManager.getBlockedRatings()) {"
+        errorLine2="                                                                   ~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/parental/ParentalControlSettings.java"
+            line="74"
+            column="68"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 28 (current min is 23): `android.media.tv.TvInputManager#getBlockedRatings`"
+        errorLine1="        mRatings = new HashSet&lt;>(mTvInputManager.getBlockedRatings());"
+        errorLine2="                                                 ~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/parental/ParentalControlSettings.java"
+            line="89"
+            column="50"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 28 (current min is 23): `android.media.tv.TvInputManager#getBlockedRatings`"
+        errorLine1="        Set&lt;TvContentRating> removed = new HashSet&lt;>(mTvInputManager.getBlockedRatings());"
+        errorLine2="                                                                     ~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/parental/ParentalControlSettings.java"
+            line="93"
+            column="70"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 28 (current min is 23): `android.media.tv.TvInputManager#getBlockedRatings`"
+        errorLine1="        added.removeAll(mTvInputManager.getBlockedRatings());"
+        errorLine2="                                        ~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/parental/ParentalControlSettings.java"
+            line="100"
+            column="41"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="            if (TvContract.isChannelUriForPassthroughInput(channelUri)) {"
+        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/SelectInputActivity.java"
+            line="69"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#isHidden`"
+        errorLine1="                if (!input.isHidden(getContext())) {"
+        errorLine2="                           ~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/ui/SelectInputView.java"
+            line="253"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#loadCustomLabel`"
+        errorLine1="        CharSequence customLabel = input.loadCustomLabel(getContext());"
+        errorLine2="                                         ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/ui/SelectInputView.java"
+            line="287"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvView#tune`"
+        errorLine1="            mTvView.tune(mInputInfo.getId(), mCurrentChannel.getUri(), params);"
+        errorLine2="                    ~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/ui/TunableTvView.java"
+            line="671"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#getTunerCount`"
+        errorLine1="                                input.getTunerCount(),"
+        errorLine2="                                      ~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/ui/TunableTvView.java"
+            line="1174"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `createScheduler`"
+        errorLine1="                mRecordingScheduler = RecordingScheduler.createScheduler(this);"
+        errorLine2="                                                         ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/TvApplication.java"
+            line="216"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#isHidden`"
+        errorLine1="                if (!input.isHidden(this)) {"
+        errorLine2="                           ~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/TvApplication.java"
+            line="402"
+            column="28"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#loadCustomLabel`"
+        errorLine1="                        CharSequence inputCustomLabel = info.loadCustomLabel(mContext);"
+        errorLine2="                                                             ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/TvInputManagerHelper.java"
+            line="216"
+            column="62"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#loadCustomLabel`"
+        errorLine1="                    CharSequence inputCustomLabel = info.loadCustomLabel(mContext);"
+        errorLine2="                                                         ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/TvInputManagerHelper.java"
+            line="257"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputManager.TvInputCallback#onInputUpdated`"
+        errorLine1="                        callback.onInputUpdated(inputId);"
+        errorLine2="                                 ~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/TvInputManagerHelper.java"
+            line="265"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#loadCustomLabel`"
+        errorLine1="                    CharSequence inputCustomLabel = inputInfo.loadCustomLabel(mContext);"
+        errorLine2="                                                              ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/TvInputManagerHelper.java"
+            line="279"
+            column="63"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputManager.TvInputCallback#onTvInputInfoUpdated`"
+        errorLine1="                        callback.onTvInputInfoUpdated(inputInfo);"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/TvInputManagerHelper.java"
+            line="284"
+            column="34"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#loadCustomLabel`"
+        errorLine1="            CharSequence customLabelCharSequence = info.loadCustomLabel(mContext);"
+        errorLine2="                                                        ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/TvInputManagerHelper.java"
+            line="472"
+            column="57"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#loadCustomLabel`"
+        errorLine1="            String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));"
+        errorLine2="                                                         ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/search/TvProviderSearch.java"
+            line="510"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputInfo#loadCustomLabel`"
+        errorLine1="            String customLabel = canonicalizeLabel(input.loadCustomLabel(mContext));"
+        errorLine2="                                                         ~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/search/TvProviderSearch.java"
+            line="535"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvContract#isChannelUriForPassthroughInput`"
+        errorLine1="        return isChannelUriForTunerInput(uri) || TvContract.isChannelUriForPassthroughInput(uri);"
+        errorLine2="                                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="packages/apps/TV/src/com/android/tv/util/Utils.java"
+            line="276"
+            column="61"/>
+    </issue>
+
+</issues>
diff --git a/partner_support/Android.bp b/partner_support/Android.bp
index 4775fc1..36364f7 100644
--- a/partner_support/Android.bp
+++ b/partner_support/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_library {
     name: "live-channels-partner-support",
     srcs: ["src/**/*.java"],
diff --git a/partner_support/sample_customization/Android.bp b/partner_support/sample_customization/Android.bp
new file mode 100644
index 0000000..f428f40
--- /dev/null
+++ b/partner_support/sample_customization/Android.bp
@@ -0,0 +1,16 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "PartnerSupportSampleCustomization",
+    optimize: {
+        enabled: false,
+    },
+    // Overlay view related functionality requires system APIs.
+    sdk_version: "system_current",
+    min_sdk_version: "23",
+    // Required for customization
+    privileged: true,
+}
diff --git a/partner_support/sample_customization/Android.mk b/partner_support/sample_customization/Android.mk
deleted file mode 100644
index 11ed13b..0000000
--- a/partner_support/sample_customization/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := PartnerSupportSampleCustomization
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PROGUARD_ENABLED := disabled
-# Overlay view related functionality requires system APIs.
-LOCAL_SDK_VERSION := system_current
-LOCAL_MIN_SDK_VERSION := 23  # M
-
-# Required for customization
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/res
-
-include $(BUILD_PACKAGE)
diff --git a/partner_support/samples/Android.bp b/partner_support/samples/Android.bp
index 9c1d2db..4e71e06 100644
--- a/partner_support/samples/Android.bp
+++ b/partner_support/samples/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_app {
     name: "PartnerSupportSampleTvInput",
 
diff --git a/partner_support/samples/AndroidManifest.xml b/partner_support/samples/AndroidManifest.xml
index e90e489..08aa231 100644
--- a/partner_support/samples/AndroidManifest.xml
+++ b/partner_support/samples/AndroidManifest.xml
@@ -16,41 +16,44 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:tools="http://schemas.android.com/tools"
-          package="com.example.partnersupportsampletvinput">
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.example.partnersupportsampletvinput">
 
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
     <!-- TODO: READ_EPG_DATA and WRITE_EPG_DATA need to be removed, once we fully
-         migrate all test environment from LMP to MNC, because the permissions
-         are not required from MNC. -->
-    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
-    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
-    <uses-permission android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT" />
-    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="23"/>
+                 migrate all test environment from LMP to MNC, because the permissions
+                 are not required from MNC. -->
+    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA"/>
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
+    <uses-permission android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT"/>
+    <uses-sdk android:targetSdkVersion="29"
+         android:minSdkVersion="23"/>
     <!--TODO(b/68949299): remove tool hint when we have smaller dependency targets-->
     <application android:label="@string/partner_support_sample_tv_input"
-            tools:replace="android:label,icon,theme,appComponentFactory"
-            android:icon="@mipmap/ic_launcher"
-            android:theme="@android:style/Theme.Holo.Light.NoActionBar"
-            android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
+         tools:replace="android:label,icon,theme,appComponentFactory"
+         android:icon="@mipmap/ic_launcher"
+         android:theme="@android:style/Theme.Holo.Light.NoActionBar"
+         android:appComponentFactory="android.support.v4.app.CoreComponentFactory">
         <activity android:name=".SampleTvInputSetupActivity"
-                android:theme="@style/Theme.Leanback.GuidedStep">
+             android:theme="@style/Theme.Leanback.GuidedStep"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
         <service android:name=".SampleTvInputService"
-            android:permission="android.permission.BIND_TV_INPUT"
-            android:label="@string/partner_support_sample_tv_input"
-            android:process="com.example.partnersupportsampletvinput">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:label="@string/partner_support_sample_tv_input"
+             android:process="com.example.partnersupportsampletvinput"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <meta-data android:name="android.media.tv.input"
-                android:resource="@xml/sampletvinputservice" />
+                 android:resource="@xml/sampletvinputservice"/>
         </service>
     </application>
 </manifest>
diff --git a/ratings/Android.bp b/ratings/Android.bp
index ced758f..fca2aee 100644
--- a/ratings/Android.bp
+++ b/ratings/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_library {
     name: "tv-ratings-resources",
     sdk_version: "system_current",
diff --git a/res/layout/menu_card_channel.xml b/res/layout/menu_card_channel.xml
index ed7b827..40e7384 100644
--- a/res/layout/menu_card_channel.xml
+++ b/res/layout/menu_card_channel.xml
@@ -32,9 +32,9 @@
             android:layout_gravity="top"
             android:scaleType="centerCrop"/>
 
-        <!-- The bottom margin specified in the redline is 8dp, but the redline doesn't consider
-             the descenders. So actually, if the bottom margin is set to 8dp, the bottom line of
-             the text lies 11dp above the bottom of the poster art due to the descenders.
+        <!-- The bottom margin specified in the UI annotation is 8dp, but the annotation doesn't
+             consider the descenders. So actually, if the bottom margin is set to 8dp, the bottom
+             line of the text lies 11dp above the bottom of the poster art due to the descenders.
              Therefore the bottom margin needs to be set to 5dp(=8dp-3dp) here.
              If the text size or font is changed, the bottom margin needs to be changed. -->
         <TextView android:id="@+id/channel_number_and_name"
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index 5cfdd19..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;
@@ -230,20 +231,20 @@
     private static final String SCREEN_BEHIND_NAME = "Behind";
 
     private static final float REFRESH_RATE_EPSILON = 0.01f;
-    private static final HashSet<Integer> BLACKLIST_KEYCODE_TO_TIS;
+    private static final HashSet<Integer> BLOCKLIST_KEYCODE_TO_TIS;
     // These keys won't be passed to TIS in addition to gamepad buttons.
     static {
-        BLACKLIST_KEYCODE_TO_TIS = new HashSet<>();
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_TV_INPUT);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MENU);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_UP);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_DOWN);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_UP);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_DOWN);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_MUTE);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MUTE);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_SEARCH);
-        BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_WINDOW);
+        BLOCKLIST_KEYCODE_TO_TIS = new HashSet<>();
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_TV_INPUT);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MENU);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_UP);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_CHANNEL_DOWN);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_UP);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_DOWN);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_VOLUME_MUTE);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_MUTE);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_SEARCH);
+        BLOCKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_WINDOW);
     }
 
     private static final IntentFilter SYSTEM_INTENT_FILTER = new IntentFilter();
@@ -330,7 +331,7 @@
     private boolean mActivityResumed;
     private boolean mActivityStarted;
     private boolean mShouldTuneToTunerChannel;
-    private boolean mUseKeycodeBlacklist;
+    private boolean mUseKeycodeBlocklist;
     private boolean mShowLockedChannelsTemporarily;
     private boolean mBackKeyPressed;
     private boolean mNeedShowBackKeyGuide;
@@ -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;
@@ -1441,16 +1446,16 @@
                 || mOverlayManager.getSideFragmentManager().isActive()) {
             return super.dispatchKeyEvent(event);
         }
-        if (BLACKLIST_KEYCODE_TO_TIS.contains(event.getKeyCode())
+        if (BLOCKLIST_KEYCODE_TO_TIS.contains(event.getKeyCode())
                 || KeyEvent.isGamepadButton(event.getKeyCode())) {
-            // If the event is in blacklisted or gamepad key, do not pass it to session.
-            // Gamepad keys are blacklisted to support TV UIs and here's the detail.
+            // If the event is in blocklisted or gamepad key, do not pass it to session.
+            // Gamepad keys are blocklisted to support TV UIs and here's the detail.
             // If there's a TIS granted RECEIVE_INPUT_EVENT, TIF sends key events to TIS
             // and return immediately saying that the event is handled.
             // In this case, fallback key will be injected but with FLAG_CANCELED
             // while gamepads support DPAD_CENTER and BACK by fallback.
             // Since we don't expect that TIS want to handle gamepad buttons now,
-            // blacklist gamepad buttons and wait for next fallback keys.
+            // blocklist gamepad buttons and wait for next fallback keys.
             // TODO: Need to consider other fallback keys (e.g. ESCAPE)
             return super.dispatchKeyEvent(event);
         }
@@ -2215,7 +2220,7 @@
                     if (event.isCanceled()) {
                         // Ignore canceled key.
                         // Note that if there's a TIS granted RECEIVE_INPUT_EVENT,
-                        // fallback keys not blacklisted will have FLAG_CANCELED.
+                        // fallback keys not blocklisted will have FLAG_CANCELED.
                         // See dispatchKeyEvent() for detail.
                         return true;
                     }
@@ -2330,7 +2335,7 @@
                     return true;
                 case KeyEvent.KEYCODE_CTRL_LEFT:
                 case KeyEvent.KEYCODE_CTRL_RIGHT:
-                    mUseKeycodeBlacklist = !mUseKeycodeBlacklist;
+                    mUseKeycodeBlocklist = !mUseKeycodeBlocklist;
                     return true;
                 case KeyEvent.KEYCODE_O:
                     mOverlayManager.getSideFragmentManager().show(new DisplayModeFragment());
diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java
index 25049f1..e7f8910 100644
--- a/src/com/android/tv/SetupPassthroughActivity.java
+++ b/src/com/android/tv/SetupPassthroughActivity.java
@@ -118,13 +118,12 @@
             setupIntent.putExtras(extras);
             try {
                 ComponentName callingActivity = getCallingActivity();
-                if (callingActivity != null
-                        && !callingActivity.getPackageName().equals(CommonConstants.BASE_PACKAGE)) {
-                    Log.w(
-                            TAG,
-                            "Calling activity "
-                                    + callingActivity.getPackageName()
-                                    + " is not trusted. Not forwarding intent.");
+                if (callingActivity == null
+                        || !callingActivity.getPackageName().equals(CommonConstants.BASE_PACKAGE)) {
+                   String name =
+                        callingActivity == null ? "null" : callingActivity.getPackageName();
+                    Log.w(TAG,
+                            "Calling activity " + name + " is not trusted. Not forwarding intent.");
                     finish();
                     return;
                 }
diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java
index f08b5e8..3167a63 100644
--- a/src/com/android/tv/TimeShiftManager.java
+++ b/src/com/android/tv/TimeShiftManager.java
@@ -110,7 +110,7 @@
     private static final int MSG_GET_CURRENT_POSITION = 1000;
     private static final int MSG_PREFETCH_PROGRAM = 1001;
     private static final long REQUEST_CURRENT_POSITION_INTERVAL = TimeUnit.SECONDS.toMillis(1);
-    private static final long MAX_DUMMY_PROGRAM_DURATION = TimeUnit.MINUTES.toMillis(30);
+    private static final long MAX_PLACEHOLDER_PROGRAM_DURATION = TimeUnit.MINUTES.toMillis(30);
     @VisibleForTesting static final long INVALID_TIME = -1;
     static final long CURRENT_TIME = -2;
     private static final long PREFETCH_TIME_OFFSET_FROM_PROGRAM_END = TimeUnit.MINUTES.toMillis(1);
@@ -489,7 +489,7 @@
         Program program = mProgramManager.getProgramAt(timeMs);
         if (program == null) {
             // Guard just in case when the program prefetch handler doesn't work on time.
-            mProgramManager.addDummyProgramsAt(timeMs);
+            mProgramManager.addPlaceholderProgramsAt(timeMs);
             program = mProgramManager.getProgramAt(timeMs);
         }
         return program;
@@ -544,8 +544,8 @@
     /**
      * Returns the current program which airs right now.
      *
-     * <p>If the program is a dummy program, which means there's no program information, returns
-     * {@code null}.
+     * <p>If the program is a placeholder program, which means there's no program information,
+     * returns {@code null}.
      */
     @Nullable
     public Program getCurrentProgram() {
@@ -909,11 +909,11 @@
                     prefetchStartTimeMs = program.getEndTimeUtcMillis();
                 } else {
                     prefetchStartTimeMs =
-                            Utils.floorTime(currentPositionMs, MAX_DUMMY_PROGRAM_DURATION);
+                            Utils.floorTime(currentPositionMs, MAX_PLACEHOLDER_PROGRAM_DURATION);
                 }
-                // Create dummy program
+                // Create placeholder program
                 mPrograms.addAll(
-                        createDummyPrograms(
+                        createPlaceholderPrograms(
                                 prefetchStartTimeMs,
                                 currentPositionMs + PREFETCH_DURATION_FOR_NEXT));
                 schedulePrefetchPrograms();
@@ -929,12 +929,12 @@
                 endTimeMs = System.currentTimeMillis();
             }
 
-            long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
+            long fetchStartTimeMs = Utils.floorTime(startTimeMs, MAX_PLACEHOLDER_PROGRAM_DURATION);
             long fetchEndTimeMs =
                     Utils.ceilTime(
-                            endTimeMs + PREFETCH_DURATION_FOR_NEXT, MAX_DUMMY_PROGRAM_DURATION);
+                            endTimeMs + PREFETCH_DURATION_FOR_NEXT, MAX_PLACEHOLDER_PROGRAM_DURATION);
             removeOutdatedPrograms(fetchStartTimeMs);
-            boolean needToLoad = addDummyPrograms(fetchStartTimeMs, fetchEndTimeMs);
+            boolean needToLoad = addPlaceholderPrograms(fetchStartTimeMs, fetchEndTimeMs);
             if (needToLoad) {
                 Range<Long> period = Range.create(fetchStartTimeMs, fetchEndTimeMs);
                 mProgramLoadQueue.add(period);
@@ -983,60 +983,60 @@
             }
         }
 
-        void addDummyProgramsAt(long timeMs) {
-            addDummyPrograms(timeMs, timeMs + PREFETCH_DURATION_FOR_NEXT);
+        void addPlaceholderProgramsAt(long timeMs) {
+            addPlaceholderPrograms(timeMs, timeMs + PREFETCH_DURATION_FOR_NEXT);
         }
 
-        private boolean addDummyPrograms(Range<Long> period) {
-            return addDummyPrograms(period.getLower(), period.getUpper());
+        private boolean addPlaceholderPrograms(Range<Long> period) {
+            return addPlaceholderPrograms(period.getLower(), period.getUpper());
         }
 
-        private boolean addDummyPrograms(long startTimeMs, long endTimeMs) {
+        private boolean addPlaceholderPrograms(long startTimeMs, long endTimeMs) {
             boolean added = false;
             if (mPrograms.isEmpty()) {
-                // Insert dummy program.
-                mPrograms.addAll(createDummyPrograms(startTimeMs, endTimeMs));
+                // Insert placeholder program.
+                mPrograms.addAll(createPlaceholderPrograms(startTimeMs, endTimeMs));
                 return true;
             }
-            // Insert dummy program to the head of the list if needed.
+            // Insert placeholder program to the head of the list if needed.
             Program firstProgram = mPrograms.get(0);
             if (startTimeMs < firstProgram.getStartTimeUtcMillis()) {
                 if (!firstProgram.isValid()) {
-                    // Already the firstProgram is dummy.
+                    // Already the firstProgram is a placeholder.
                     mPrograms.remove(0);
                     mPrograms.addAll(
                             0,
-                            createDummyPrograms(startTimeMs, firstProgram.getEndTimeUtcMillis()));
+                            createPlaceholderPrograms(startTimeMs, firstProgram.getEndTimeUtcMillis()));
                 } else {
                     mPrograms.addAll(
                             0,
-                            createDummyPrograms(startTimeMs, firstProgram.getStartTimeUtcMillis()));
+                            createPlaceholderPrograms(startTimeMs, firstProgram.getStartTimeUtcMillis()));
                 }
                 added = true;
             }
-            // Insert dummy program to the tail of the list if needed.
+            // Insert placeholder program to the tail of the list if needed.
             Program lastProgram = mPrograms.get(mPrograms.size() - 1);
             if (endTimeMs > lastProgram.getEndTimeUtcMillis()) {
                 if (!lastProgram.isValid()) {
-                    // Already the lastProgram is dummy.
+                    // Already the lastProgram is a placeholder.
                     mPrograms.remove(mPrograms.size() - 1);
                     mPrograms.addAll(
-                            createDummyPrograms(lastProgram.getStartTimeUtcMillis(), endTimeMs));
+                            createPlaceholderPrograms(lastProgram.getStartTimeUtcMillis(), endTimeMs));
                 } else {
                     mPrograms.addAll(
-                            createDummyPrograms(lastProgram.getEndTimeUtcMillis(), endTimeMs));
+                            createPlaceholderPrograms(lastProgram.getEndTimeUtcMillis(), endTimeMs));
                 }
                 added = true;
             }
-            // Insert dummy programs if the holes exist in the list.
+            // Insert placeholder programs if the holes exist in the list.
             for (int i = 1; i < mPrograms.size(); ++i) {
                 long endOfPrevious = mPrograms.get(i - 1).getEndTimeUtcMillis();
                 long startOfCurrent = mPrograms.get(i).getStartTimeUtcMillis();
                 if (startOfCurrent > endOfPrevious) {
-                    List<Program> dummyPrograms =
-                            createDummyPrograms(endOfPrevious, startOfCurrent);
-                    mPrograms.addAll(i, dummyPrograms);
-                    i += dummyPrograms.size();
+                    List<Program> placeholderPrograms =
+                            createPlaceholderPrograms(endOfPrevious, startOfCurrent);
+                    mPrograms.addAll(i, placeholderPrograms);
+                    i += placeholderPrograms.size();
                     added = true;
                 }
             }
@@ -1049,7 +1049,7 @@
             }
         }
 
-        private void removeDummyPrograms() {
+        private void removePlaceholderPrograms() {
             for (Iterator<Program> it = mPrograms.listIterator(); it.hasNext(); ) {
                 if (!it.next().isValid()) {
                     it.remove();
@@ -1084,18 +1084,18 @@
             }
         }
 
-        // Returns a list of dummy programs.
-        // The maximum duration of a dummy program is {@link MAX_DUMMY_PROGRAM_DURATION}.
+        // Returns a list of placeholder programs.
+        // The maximum duration of a placeholder program is {@link MAX_PLACEHOLDER_PROGRAM_DURATION}.
         // So if the duration ({@code endTimeMs}-{@code startTimeMs}) is greater than the duration,
-        // we need to create multiple dummy programs.
+        // we need to create multiple placeholder programs.
         // The reason of the limitation of the duration is because we want the trick play viewer
-        // to show the time-line duration of {@link MAX_DUMMY_PROGRAM_DURATION} at most
-        // for a dummy program.
-        private List<Program> createDummyPrograms(long startTimeMs, long endTimeMs) {
+        // to show the time-line duration of {@link MAX_PLACEHOLDER_PROGRAM_DURATION} at most
+        // for a placeholder program.
+        private List<Program> createPlaceholderPrograms(long startTimeMs, long endTimeMs) {
             SoftPreconditions.checkArgument(
                     endTimeMs - startTimeMs <= TWO_WEEKS_MS,
                     TAG,
-                    "createDummyProgram: long duration of dummy programs are requested ( %s , %s)",
+                    "createPlaceholderProgram: long duration of placeholder programs are requested ( %s , %s)",
                     Utils.toTimeString(startTimeMs),
                     Utils.toTimeString(endTimeMs));
             if (startTimeMs >= endTimeMs) {
@@ -1103,7 +1103,7 @@
             }
             List<Program> programs = new ArrayList<>();
             long start = startTimeMs;
-            long end = Utils.ceilTime(startTimeMs, MAX_DUMMY_PROGRAM_DURATION);
+            long end = Utils.ceilTime(startTimeMs, MAX_PLACEHOLDER_PROGRAM_DURATION);
             while (end < endTimeMs) {
                 programs.add(
                         new ProgramImpl.Builder()
@@ -1111,7 +1111,7 @@
                                 .setEndTimeUtcMillis(end)
                                 .build());
                 start = end;
-                end += MAX_DUMMY_PROGRAM_DURATION;
+                end += MAX_PLACEHOLDER_PROGRAM_DURATION;
             }
             programs.add(
                     new ProgramImpl.Builder()
@@ -1256,7 +1256,7 @@
                 }
                 if (programs == null || programs.isEmpty()) {
                     mEmptyFetchCount++;
-                    if (addDummyPrograms(mPeriod)) {
+                    if (addPlaceholderPrograms(mPeriod)) {
                         TimeShiftManager.this.onProgramInfoChanged();
                     }
                     schedulePrefetchPrograms();
@@ -1265,7 +1265,7 @@
                 }
                 mEmptyFetchCount = 0;
                 if (!mPrograms.isEmpty()) {
-                    removeDummyPrograms();
+                    removePlaceholderPrograms();
                     removeOverlappedPrograms(programs);
                     Program loadedProgram = programs.get(0);
                     for (int i = 0; i < mPrograms.size() && !programs.isEmpty(); ++i) {
@@ -1282,7 +1282,7 @@
                     }
                 }
                 mPrograms.addAll(programs);
-                addDummyPrograms(mPeriod);
+                addPlaceholderPrograms(mPeriod);
                 TimeShiftManager.this.onProgramInfoChanged();
                 schedulePrefetchPrograms();
                 startNextLoadingIfNeeded();
diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java
index a866c78..dcfa69e 100644
--- a/src/com/android/tv/data/ProgramDataManager.java
+++ b/src/com/android/tv/data/ProgramDataManager.java
@@ -402,7 +402,7 @@
      *
      * <p>Prefetch should be enabled to call it.
      *
-     * @return {@link List} with Programs. It may includes dummy program if the entry needs DB
+     * @return {@link List} with Programs. It may includes stub program if the entry needs DB
      *     operations to get.
      */
     public List<Program> getPrograms(long channelId, long startTime) {
@@ -425,7 +425,7 @@
     private int getProgramIndexAt(List<Program> programs, long time) {
         Program key = mZeroLengthProgramCache.get(time);
         if (key == null) {
-            key = createDummyProgram(time, time);
+            key = createStubProgram(time, time);
             mZeroLengthProgramCache.put(time, key);
         }
         int index = Collections.binarySearch(programs, key);
@@ -527,11 +527,11 @@
                 continue;
             }
 
-            // Update dummy program around current program if any.
+            // Update stub program around current program if any.
             if (cachedProgram.getStartTimeUtcMillis() < currentProgram.getStartTimeUtcMillis()) {
-                // The dummy program starts earlier than the current program. Adjust its end time.
+                // The stub program starts earlier than the current program. Adjust its end time.
                 i.set(
-                        createDummyProgram(
+                        createStubProgram(
                                 cachedProgram.getStartTimeUtcMillis(),
                                 currentProgram.getStartTimeUtcMillis()));
                 i.add(currentProgram);
@@ -539,9 +539,9 @@
                 i.set(currentProgram);
             }
             if (currentProgram.getEndTimeUtcMillis() < cachedProgram.getEndTimeUtcMillis()) {
-                // The dummy program ends later than the current program. Adjust its start time.
+                // The stub program ends later than the current program. Adjust its start time.
                 i.add(
-                        createDummyProgram(
+                        createStubProgram(
                                 currentProgram.getEndTimeUtcMillis(),
                                 cachedProgram.getEndTimeUtcMillis()));
             }
@@ -1010,8 +1010,8 @@
         }
     }
 
-    // Create dummy program which indicates data isn't loaded yet so DB query is required.
-    private Program createDummyProgram(long startTimeMs, long endTimeMs) {
+    // Create stub program which indicates data isn't loaded yet so DB query is required.
+    private Program createStubProgram(long startTimeMs, long endTimeMs) {
         return new ProgramImpl.Builder()
                 .setChannelId(Channel.INVALID_ID)
                 .setStartTimeUtcMillis(startTimeMs)
diff --git a/src/com/android/tv/data/ProgramImpl.java b/src/com/android/tv/data/ProgramImpl.java
index 5097e2d..84e42fb 100644
--- a/src/com/android/tv/data/ProgramImpl.java
+++ b/src/com/android/tv/data/ProgramImpl.java
@@ -419,7 +419,7 @@
 
     @Override
     public int hashCode() {
-        // Hash with all the properties because program ID can be invalid for the dummy programs.
+        // Hash with all the properties because program ID can be invalid for the stub programs.
         return Objects.hash(
                 mChannelId,
                 mStartTimeUtcMillis,
@@ -446,7 +446,7 @@
         if (!(other instanceof ProgramImpl)) {
             return false;
         }
-        // Compare all the properties because program ID can be invalid for the dummy programs.
+        // Compare all the properties because program ID can be invalid for the stub programs.
         ProgramImpl program = (ProgramImpl) other;
         return Objects.equals(mPackageName, program.mPackageName)
                 && mChannelId == program.mChannelId
diff --git a/src/com/android/tv/dvr/data/RecordedProgram.java b/src/com/android/tv/dvr/data/RecordedProgram.java
index 6143055..230ec62 100644
--- a/src/com/android/tv/dvr/data/RecordedProgram.java
+++ b/src/com/android/tv/dvr/data/RecordedProgram.java
@@ -113,7 +113,7 @@
                         .setPosterArtUri(StringUtils.nullToEmpty(cursor.getString(index++)))
                         .setThumbnailUri(StringUtils.nullToEmpty(cursor.getString(index++)))
                         .setSearchable(cursor.getInt(index++) == 1)
-                        .setDataUri(cursor.getString(index++))
+                        .setDataUri(StringUtils.nullToEmpty(cursor.getString(index++)))
                         .setDataBytes(cursor.getLong(index++))
                         .setDurationMillis(cursor.getLong(index++))
                         .setExpireTimeUtcMillis(cursor.getLong(index++))
diff --git a/src/com/android/tv/features/TvFeatures.java b/src/com/android/tv/features/TvFeatures.java
index a18d9c8..5282c28 100644
--- a/src/com/android/tv/features/TvFeatures.java
+++ b/src/com/android/tv/features/TvFeatures.java
@@ -98,8 +98,8 @@
     /** Enable a conflict dialog between currently watched channel and upcoming recording. */
     public static final Feature SHOW_UPCOMING_CONFLICT_DIALOG = OFF;
 
-    /** Use input blacklist to disable partner's tuner input. */
-    public static final Feature USE_PARTNER_INPUT_BLACKLIST = ON;
+    /** Use input blocklist to disable partner's tuner input. */
+    public static final Feature USE_PARTNER_INPUT_BLOCKLIST = ON;
 
     private TvFeatures() {}
 }
diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java
index 516a4d9..9dfc05c 100644
--- a/src/com/android/tv/guide/ProgramManager.java
+++ b/src/com/android/tv/guide/ProgramManager.java
@@ -417,8 +417,9 @@
 
     /**
      * Returns an entry as {@link ProgramImpl} for a given {@code channelId} and {@code index} of
-     * entries within the currently managed time range. Returned {@link ProgramImpl} can be a dummy
-     * one (e.g., whose channelId is INVALID_ID), when it corresponds to a gap between programs.
+     * entries within the currently managed time range. Returned {@link ProgramImpl} can be a
+     * placeholder (e.g., whose channelId is INVALID_ID), when it corresponds to a gap between
+     * programs.
      */
     TableEntry getTableEntry(long channelId, int index) {
         mProgramDataManager.prefetchChannel(channelId, index);
@@ -613,7 +614,7 @@
             List<Program> programs = mProgramDataManager.getPrograms(channelId, mStartUtcMillis);
             for (Program program : programs) {
                 if (program.getChannelId() == INVALID_ID) {
-                    // Dummy program.
+                    // Placeholder program.
                     continue;
                 }
                 long programStartTime = Math.max(program.getStartTimeUtcMillis(), mStartUtcMillis);
diff --git a/src/com/android/tv/menu/MenuRowView.java b/src/com/android/tv/menu/MenuRowView.java
index e09a4ef..27554c2 100644
--- a/src/com/android/tv/menu/MenuRowView.java
+++ b/src/com/android/tv/menu/MenuRowView.java
@@ -95,7 +95,8 @@
                     @Override
                     public void sendAccessibilityEvent(View host, int eventType) {
                         super.sendAccessibilityEvent(host, eventType);
-                        if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED &&
+                        if ((eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED ||
+                                eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) &&
                                 !mRow.isReselected()) {
                             requestChildFocus();
                         }
diff --git a/src/com/android/tv/menu/MenuView.java b/src/com/android/tv/menu/MenuView.java
index add4a77..e1a3379 100644
--- a/src/com/android/tv/menu/MenuView.java
+++ b/src/com/android/tv/menu/MenuView.java
@@ -25,6 +25,8 @@
 import android.view.ViewParent;
 import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import com.android.tv.menu.Menu.MenuShowReason;
 import java.util.ArrayList;
@@ -192,7 +194,12 @@
     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
         int selectedPosition = mLayoutManager.getSelectedPosition();
         // When the menu shows up, the selected row should have focus.
+        AccessibilityManager mAccessibilityManager =
+                getContext().getSystemService(AccessibilityManager.class);
         if (selectedPosition >= 0 && selectedPosition < mMenuRowViews.size()) {
+            if(mAccessibilityManager.isEnabled())
+                mMenuRowViews.get(selectedPosition)
+                        .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
             return mMenuRowViews.get(selectedPosition).requestFocus();
         }
         return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
diff --git a/src/com/android/tv/parental/ContentRatingSystem.java b/src/com/android/tv/parental/ContentRatingSystem.java
index d85dd50..b40c113 100644
--- a/src/com/android/tv/parental/ContentRatingSystem.java
+++ b/src/com/android/tv/parental/ContentRatingSystem.java
@@ -288,7 +288,7 @@
                 ratings.add(builder.build(subRatings));
             }
 
-            // Sanity check.
+            // Soundness check.
             for (SubRating subRating : subRatings) {
                 boolean used = false;
                 for (Rating rating : ratings) {
diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java
index 1652bd7..870b3c1 100644
--- a/src/com/android/tv/recommendation/NotificationService.java
+++ b/src/com/android/tv/recommendation/NotificationService.java
@@ -437,7 +437,8 @@
         Intent intent = new Intent(Intent.ACTION_VIEW, channel.getUri());
         intent.putExtra(TUNE_PARAMS_RECOMMENDATION_TYPE, mRecommendationType);
         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        final PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent, 0);
+        final PendingIntent notificationIntent = PendingIntent.getActivity(this, 0, intent,
+                PendingIntent.FLAG_IMMUTABLE);
 
         // This callback will run on the main thread.
         Bitmap largeIconBitmap =
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/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java
index 23c9b49..72d527a 100644
--- a/src/com/android/tv/util/TvInputManagerHelper.java
+++ b/src/com/android/tv/util/TvInputManagerHelper.java
@@ -128,14 +128,14 @@
 
     private static final String PERMISSION_ACCESS_ALL_EPG_DATA =
             "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA";
-    private static final String[] mPhysicalTunerBlackList = {
+    private static final String[] mPhysicalTunerBlockList = {
         "com.google.android.videos", // Play Movies
     };
     private static final String META_LABEL_SORT_KEY = "input_sort_key";
 
     private static final String TV_INPUT_ALLOW_3RD_PARTY_INPUTS = "tv_input_allow_3rd_party_inputs";
 
-    private static final String[] SYSTEM_INPUT_ID_BLACKLIST = {
+    private static final String[] SYSTEM_INPUT_ID_BLOCKLIST = {
         "com.google.android.videos/" // Play Movies
     };
 
@@ -160,7 +160,7 @@
         DEFAULT_TV_INPUT_PRIORITY.add(TvInputInfo.TYPE_OTHER);
     }
 
-    private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLACKLIST = {
+    private static final String[] PARTNER_TUNER_INPUT_PREFIX_BLOCKLIST = {
         /* Begin_AOSP_Comment_Out
         // Disabled partner's tuner input prefix list.
         "com.mediatek.tvinput/.dtv"
@@ -597,7 +597,7 @@
 
     private boolean isInputPhysicalTuner(TvInputInfo input) {
         String packageName = input.getServiceInfo().packageName;
-        if (Arrays.asList(mPhysicalTunerBlackList).contains(packageName)) {
+        if (Arrays.asList(mPhysicalTunerBlockList).contains(packageName)) {
             return false;
         }
 
@@ -628,9 +628,9 @@
         return true;
     }
 
-    private boolean isInBlackList(String inputId) {
-        if (TvFeatures.USE_PARTNER_INPUT_BLACKLIST.isEnabled(mContext)) {
-            for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLACKLIST) {
+    private boolean isBlocked(String inputId) {
+        if (TvFeatures.USE_PARTNER_INPUT_BLOCKLIST.isEnabled(mContext)) {
+            for (String disabledTunerInputPrefix : PARTNER_TUNER_INPUT_PREFIX_BLOCKLIST) {
                 if (inputId.contains(disabledTunerInputPrefix)) {
                     return true;
                 }
@@ -694,13 +694,13 @@
             if (!isSystemInput(info)) {
                 return true;
             }
-            for (String id : SYSTEM_INPUT_ID_BLACKLIST) {
+            for (String id : SYSTEM_INPUT_ID_BLOCKLIST) {
                 if (info.getId().startsWith(id)) {
                     return true;
                 }
             }
         }
-        return isInBlackList(info.getId());
+        return isBlocked(info.getId());
     }
 
     /**
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 1abaaf7..5a65538 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_library {
     name: "tv-test-common",
 
diff --git a/tests/common/Android.mk b/tests/common/Android.mk
index ff0934b..7a232ff 100644
--- a/tests/common/Android.mk
+++ b/tests/common/Android.mk
@@ -2,6 +2,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := tv-test-common-robo
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src/com/android/tv/testing/robo) \
@@ -15,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/func/Android.bp b/tests/func/Android.bp
new file mode 100644
index 0000000..d408539
--- /dev/null
+++ b/tests/func/Android.bp
@@ -0,0 +1,21 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "TVFuncTests",
+    // Include all test java files.
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.runner",
+        "tv-test-common",
+        "ub-uiautomator",
+    ],
+    libs: ["android.test.base.stubs"],
+    instrumentation_for: "LiveTv",
+    sdk_version: "system_current",
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/tests/func/Android.mk b/tests/func/Android.mk
deleted file mode 100644
index 53c869e..0000000
--- a/tests/func/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := TVFuncTests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.runner \
-    tv-test-common \
-    ub-uiautomator \
-
-LOCAL_JAVA_LIBRARIES := android.test.base.stubs
-
-LOCAL_INSTRUMENTATION_FOR := LiveTv
-
-LOCAL_SDK_VERSION := system_current
-
-LOCAL_PROGUARD_ENABLED := disabled
-include $(BUILD_PACKAGE)
diff --git a/tests/input/Android.bp b/tests/input/Android.bp
new file mode 100644
index 0000000..31e9e09
--- /dev/null
+++ b/tests/input/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 Google Inc.
+//
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+    ],
+}
+
+android_app {
+    name: "TVTestInput",
+    srcs: ["src/**/*.java"],
+    optimize: {
+        enabled: false,
+    },
+    // Overlay view related functionality requires system APIs.
+    sdk_version: "system_current",
+    static_libs: [
+        "tv-test-common",
+        "tv-common",
+    ],
+    // Disable dexpreopt and <uses-library> check for test.
+    enforce_uses_libs: false,
+    dex_preopt: {
+        enabled: false,
+    },
+    resource_dirs: [
+        "res",
+    ],
+    aaptflags: [
+        "--auto-add-overlay",
+        "--extra-packages com.android.tv.testing",
+    ],
+}
diff --git a/tests/input/Android.mk b/tests/input/Android.mk
deleted file mode 100644
index 46b5621..0000000
--- a/tests/input/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := TVTestInput
-LOCAL_MODULE_TAGS := optional
-LOCAL_PROGUARD_ENABLED := disabled
-# Overlay view related functionality requires system APIs.
-LOCAL_SDK_VERSION := system_current
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    tv-test-common \
-    tv-common
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := --auto-add-overlay \
-    --extra-packages com.android.tv.testing
-
-include $(BUILD_PACKAGE)
-
-ifneq ($(filter TV,$(TARGET_BUILD_APPS)),)
-  $(call dist-for-goals,apps_only,$(LOCAL_BUILT_MODULE):$(LOCAL_PACKAGE_NAME).apk)
-endif
-
diff --git a/tests/input/AndroidManifest.xml b/tests/input/AndroidManifest.xml
index e01b913..b0c8aaa 100644
--- a/tests/input/AndroidManifest.xml
+++ b/tests/input/AndroidManifest.xml
@@ -16,55 +16,55 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.tv.testinput">
+     package="com.android.tv.testinput">
 
-    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="23"/>
+    <uses-sdk android:targetSdkVersion="29"
+         android:minSdkVersion="23"/>
 
      <!-- Required to update or read existing channel and program information in TvProvider. -->
-    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA"/>
     <!-- Required to update channel and program information in TvProvider. -->
-    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
 
     <application android:label="@string/sample_tv_input"
-            android:icon="@drawable/android_48dp"
-            android:theme="@android:style/Theme.Holo.Light.NoActionBar" >
+         android:icon="@drawable/android_48dp"
+         android:theme="@android:style/Theme.Holo.Light.NoActionBar">
         <!-- Launched by the TV app before it uses TestTvInputService to set up channels for this
-        input. -->
-        <activity android:name=".TestTvInputSetupActivity" >
+                    input. -->
+        <activity android:name=".TestTvInputSetupActivity"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
         <service android:name=".TestTvInputService"
-            android:permission="android.permission.BIND_TV_INPUT"
-            android:label="@string/simple_input_label">
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:label="@string/simple_input_label"
+             android:exported="true">
             <!-- Required filter used by the system to launch our account service. -->
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
             <!-- An XML file which describes this input. This provides a pointer to the
-            TestTvInputSetupActivity to the system/TV app. -->
+                            TestTvInputSetupActivity to the system/TV app. -->
             <meta-data android:name="android.media.tv.input"
-                android:resource="@xml/testtvinputservice" />
+                 android:resource="@xml/testtvinputservice"/>
         </service>
-        <service android:name=".TestInputControlService" android:exported="true"/>
+        <service android:name=".TestInputControlService"
+             android:exported="true"/>
 
     </application>
 
-    <instrumentation
-            android:name=".instrument.TestSetupInstrumentation"
-            android:label="Test Setup Instrument"
-            android:targetPackage="com.android.tv.testinput" />
+    <instrumentation android:name=".instrument.TestSetupInstrumentation"
+         android:label="Test Setup Instrument"
+         android:targetPackage="com.android.tv.testinput"/>
 
-    <uses-feature
-        android:name="android.hardware.touchscreen"
-        android:required="false" />
-    <uses-feature
-        android:name="android.software.leanback"
-        android:required="true" />
+    <uses-feature android:name="android.hardware.touchscreen"
+         android:required="false"/>
+    <uses-feature android:name="android.software.leanback"
+         android:required="true"/>
     <!-- Required to expose this app in the store only when the device has TV input framework
-    with the TV app. -->
-    <uses-feature
-        android:name="android.software.live_tv"
-        android:required="true" />
+            with the TV app. -->
+    <uses-feature android:name="android.software.live_tv"
+         android:required="true"/>
 </manifest>
diff --git a/tests/jank/Android.bp b/tests/jank/Android.bp
new file mode 100644
index 0000000..1cea734
--- /dev/null
+++ b/tests/jank/Android.bp
@@ -0,0 +1,22 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "TVJankTests",
+    // Include all test java files.
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.runner",
+        "tv-test-common",
+        "ub-janktesthelper",
+        "ub-uiautomator",
+    ],
+    libs: ["android.test.base.stubs"],
+    instrumentation_for: "LiveTv",
+    sdk_version: "system_current",
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/tests/jank/Android.mk b/tests/jank/Android.mk
deleted file mode 100644
index 7df77ea..0000000
--- a/tests/jank/Android.mk
+++ /dev/null
@@ -1,25 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := TVJankTests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.runner \
-    tv-test-common \
-    ub-janktesthelper \
-    ub-uiautomator \
-
-LOCAL_JAVA_LIBRARIES := android.test.base.stubs
-
-LOCAL_INSTRUMENTATION_FOR := LiveTv
-
-LOCAL_SDK_VERSION := system_current
-LOCAL_PROGUARD_ENABLED := disabled
-
-include $(BUILD_PACKAGE)
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index 2a6bdd1..c5341ab 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -5,6 +5,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := TvRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 
 BASE_DIR = src/com/android/tv
@@ -50,6 +52,8 @@
 #############################################################
 include $(CLEAR_VARS)
 LOCAL_MODULE := RunTvRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 
 BASE_DIR = com/android/tv
 EXCLUDE_FILES := \
diff --git a/tests/robotests/src/com/android/tv/SetupPassthroughActivityTest.java b/tests/robotests/src/com/android/tv/SetupPassthroughActivityTest.java
index efba494..2b2bbe8 100644
--- a/tests/robotests/src/com/android/tv/SetupPassthroughActivityTest.java
+++ b/tests/robotests/src/com/android/tv/SetupPassthroughActivityTest.java
@@ -37,6 +37,7 @@
 import com.android.tv.common.dagger.ApplicationModule;
 import com.android.tv.common.flags.impl.DefaultLegacyFlags;
 import com.android.tv.common.flags.impl.SettableFlagsModule;
+import com.android.tv.common.flags.proto.TypedFeatures.StringListParam;
 import com.android.tv.common.util.CommonUtils;
 import com.android.tv.data.ChannelDataManager;
 import com.android.tv.data.epg.EpgFetcher;
@@ -53,7 +54,6 @@
 
 import com.google.android.tv.partner.support.EpgContract;
 import com.google.common.base.Optional;
-import com.android.tv.common.flags.proto.TypedFeatures.StringListParam;
 
 import dagger.Component;
 import dagger.Module;
@@ -71,7 +71,6 @@
 import org.mockito.Mockito;
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 import org.robolectric.android.controller.ActivityController;
 import org.robolectric.android.util.concurrent.RoboExecutorService;
 import org.robolectric.annotation.Config;
@@ -169,8 +168,7 @@
                         CommonUtils.createSetupIntent(new Intent(), testInput.getId()));
         SetupPassthroughActivity activity = activityController.get();
         ShadowActivity shadowActivity = shadowOf(activity);
-        shadowActivity.setCallingActivity(
-                new ComponentName(CommonConstants.BASE_PACKAGE, "com.example.MyClass"));
+        shadowActivity.setCallingActivity(createTrustedComponent());
         activityController.create();
 
         ShadowActivity.IntentForResult shadowIntent =
@@ -205,6 +203,27 @@
     }
 
     @Test
+    public void create_nullCallingPackage() {
+        testSingletonApp.tvInputManagerHelper.start();
+        testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
+
+        ActivityController<SetupPassthroughActivity> activityController =
+                Robolectric.buildActivity(
+                        SetupPassthroughActivity.class,
+                        CommonUtils.createSetupIntent(new Intent(), testInput.getId()));
+        SetupPassthroughActivity activity = activityController.get();
+        ShadowActivity shadowActivity = shadowOf(activity);
+        shadowActivity.setCallingActivity(null);
+        activityController.create();
+
+        ShadowActivity.IntentForResult shadowIntent =
+                shadowActivity.getNextStartedActivityForResult();
+        // Since the calling activity is null, the next activity should not be started.
+        assertThat(shadowIntent).isNull();
+        assertThat(activity.isFinishing()).isTrue();
+    }
+
+    @Test
     public void onActivityResult_canceled() {
         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
@@ -216,7 +235,7 @@
 
     @Test
     public void onActivityResult_ok() {
-        TestSetupUtils setupUtils = new TestSetupUtils(RuntimeEnvironment.application);
+        TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
         testSingletonApp.setupUtils = setupUtils;
         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
@@ -234,7 +253,7 @@
     @Test
     public void onActivityResult_3rdPartyEpg_ok() {
         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest();
-        TestSetupUtils setupUtils = new TestSetupUtils(RuntimeEnvironment.application);
+        TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
         testSingletonApp.setupUtils = setupUtils;
         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
         testSingletonApp
@@ -257,9 +276,9 @@
     }
 
     @Test
-    public void onActivityResult_3rdPartyEpg_notWhiteListed() {
+    public void onActivityResult_3rdPartyEpg_notAllowed() {
         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.enableForTest();
-        TestSetupUtils setupUtils = new TestSetupUtils(RuntimeEnvironment.application);
+        TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
         testSingletonApp.setupUtils = setupUtils;
         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
         SetupPassthroughActivity activity = createSetupActivityFor(testInput.getId());
@@ -280,7 +299,7 @@
     @Test
     public void onActivityResult_3rdPartyEpg_disabled() {
         TvFeatures.CLOUD_EPG_FOR_3RD_PARTY.disableForTests();
-        TestSetupUtils setupUtils = new TestSetupUtils(RuntimeEnvironment.application);
+        TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
         testSingletonApp.setupUtils = setupUtils;
         testSingletonApp.tvInputManagerHelper.getFakeTvInputManager().add(testInput, -1);
         testSingletonApp
@@ -305,7 +324,7 @@
 
     @Test
     public void onActivityResult_ok_tvInputInfo_null() {
-        TestSetupUtils setupUtils = new TestSetupUtils(RuntimeEnvironment.application);
+        TestSetupUtils setupUtils = new TestSetupUtils(ApplicationProvider.getApplicationContext());
         testSingletonApp.setupUtils = setupUtils;
         FakeTvInputManager tvInputManager =
                 testSingletonApp.tvInputManagerHelper.getFakeTvInputManager();
@@ -320,11 +339,15 @@
     }
 
     private SetupPassthroughActivity createSetupActivityFor(String inputId) {
-        return Robolectric.buildActivity(
+        ActivityController<SetupPassthroughActivity> activityController =
+                Robolectric.buildActivity(
                         SetupPassthroughActivity.class,
-                        CommonUtils.createSetupIntent(new Intent(), inputId))
-                .create()
-                .get();
+                        CommonUtils.createSetupIntent(new Intent(), inputId));
+        SetupPassthroughActivity activity = activityController.get();
+        ShadowActivity shadowActivity = shadowOf(activity);
+        shadowActivity.setCallingActivity(createTrustedComponent());
+        activityController.create();
+        return activity;
     }
 
     private TvInputInfo createMockInput(String inputId) {
@@ -344,6 +367,10 @@
         return tvInputInfo;
     }
 
+    private static ComponentName createTrustedComponent() {
+        return new ComponentName(CommonConstants.BASE_PACKAGE, "com.example.MyClass");
+    }
+
     /**
      * Test SetupUtils.
      *
@@ -351,6 +378,7 @@
      * bypasses all of that.
      */
     private static class TestSetupUtils extends SetupUtils {
+
         public String finishedId;
         public Runnable finishedRunnable;
 
@@ -413,6 +441,7 @@
             })
     /** Module for {@link MyTestApp} */
     static class TestModule {
+
         private final MyTestApp myTestApp;
 
         TestModule(MyTestApp test) {
diff --git a/tests/unit/Android.mk b/tests/unit/Android.mk
index 80dbfee..6123af7 100644
--- a/tests/unit/Android.mk
+++ b/tests/unit/Android.mk
@@ -21,6 +21,8 @@
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/../common/res
 
 LOCAL_PACKAGE_NAME := TVUnitTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 
 LOCAL_INSTRUMENTATION_FOR := LiveTv
 
@@ -28,4 +30,3 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 include $(BUILD_PACKAGE)
-
diff --git a/tuner/Android.bp b/tuner/Android.bp
index bbafef2..d094c45 100644
--- a/tuner/Android.bp
+++ b/tuner/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_library {
     name: "live-tv-tuner",
     srcs: ["src/**/*.java"],
diff --git a/tuner/SampleDvbTuner/Android.bp b/tuner/SampleDvbTuner/Android.bp
index 29a177f..578c051 100644
--- a/tuner/SampleDvbTuner/Android.bp
+++ b/tuner/SampleDvbTuner/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_app {
     name: "SampleDvbTuner",
 
diff --git a/tuner/SampleDvbTuner/AndroidManifest.xml b/tuner/SampleDvbTuner/AndroidManifest.xml
index 3e31517..5b4e12c 100755
--- a/tuner/SampleDvbTuner/AndroidManifest.xml
+++ b/tuner/SampleDvbTuner/AndroidManifest.xml
@@ -14,79 +14,72 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.tv.tuner.sample.dvb" >
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.tv.tuner.sample.dvb">
 
-    <uses-sdk
-        android:minSdkVersion="23"
-        android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="29"/>
 
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS" />
-    <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="com.android.providers.tv.permission.READ_EPG_DATA" />
-    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
+    <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="com.android.providers.tv.permission.READ_EPG_DATA"/>
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
     <!-- Permission to modify Recorded Program -->
-    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
 
     <!-- Permissions/feature for USB tuner -->
-    <uses-permission android:name="android.permission.DVB_DEVICE" />
+    <uses-permission android:name="android.permission.DVB_DEVICE"/>
 
-    <uses-feature
-        android:name="android.hardware.usb.host"
-        android:required="false" />
+    <uses-feature android:name="android.hardware.usb.host"
+         android:required="false"/>
 
     <!-- Limit only for Android TV -->
-    <uses-feature
-        android:name="android.software.leanback"
-        android:required="true" />
-    <uses-feature
-        android:name="android.software.live_tv"
-        android:required="true" />
-    <uses-feature
-        android:name="android.hardware.touchscreen"
-        android:required="false" />
+    <uses-feature android:name="android.software.leanback"
+         android:required="true"/>
+    <uses-feature android:name="android.software.live_tv"
+         android:required="true"/>
+    <uses-feature android:name="android.hardware.touchscreen"
+         android:required="false"/>
 
     <application tools:replace="android:appComponentFactory"
-        android:name="com.android.tv.tuner.sample.dvb.app.SampleDvbTuner"
-        android:appComponentFactory="android.support.v4.app.CoreComponentFactory"
-        android:icon="@mipmap/ic_launcher"
-        android:label="@string/sample_dvb_tuner_app_name" >
+         android:name="com.android.tv.tuner.sample.dvb.app.SampleDvbTuner"
+         android:appComponentFactory="android.support.v4.app.CoreComponentFactory"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/sample_dvb_tuner_app_name">
 
-        <activity
-            android:name="com.android.tv.tuner.sample.dvb.setup.SampleDvbTunerSetupActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:exported="true"
-            android:label="@string/sample_dvb_tuner_app_name"
-            android:launchMode="singleInstance"
-            android:theme="@style/Theme.Setup.GuidedStep" >
+        <activity android:name="com.android.tv.tuner.sample.dvb.setup.SampleDvbTunerSetupActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:exported="true"
+             android:label="@string/sample_dvb_tuner_app_name"
+             android:launchMode="singleInstance"
+             android:theme="@style/Theme.Setup.GuidedStep">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name="com.android.tv.tuner.sample.dvb.tvinput.SampleDvbTunerTvInputService"
-            android:label="@string/sample_dvb_tuner_app_name"
-            android:permission="android.permission.BIND_TV_INPUT"
-            android:process="com.android.tv.tuner.sample.dvb.tvinput" >
+        <service android:name="com.android.tv.tuner.sample.dvb.tvinput.SampleDvbTunerTvInputService"
+             android:label="@string/sample_dvb_tuner_app_name"
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:process="com.android.tv.tuner.sample.dvb.tvinput"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.media.tv.input"
-                android:resource="@xml/sample_dvb_tvinputservice" />
+            <meta-data android:name="android.media.tv.input"
+                 android:resource="@xml/sample_dvb_tvinputservice"/>
         </service>
-        <service
-            android:name="com.android.tv.tuner.tvinput.TunerStorageCleanUpService"
-            android:exported="false"
-            android:permission="android.permission.BIND_JOB_SERVICE"
-            android:process="com.android.tv.tuner" />
+        <service android:name="com.android.tv.tuner.tvinput.TunerStorageCleanUpService"
+             android:exported="false"
+             android:permission="android.permission.BIND_JOB_SERVICE"
+             android:process="com.android.tv.tuner"/>
     </application>
 
 </manifest>
diff --git a/tuner/SampleNetworkTuner/Android.bp b/tuner/SampleNetworkTuner/Android.bp
index 4c2b344..f4accb4 100644
--- a/tuner/SampleNetworkTuner/Android.bp
+++ b/tuner/SampleNetworkTuner/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_app {
     name: "SampleNetworkTuner",
 
diff --git a/tuner/SampleNetworkTuner/AndroidManifest.xml b/tuner/SampleNetworkTuner/AndroidManifest.xml
index 348bfaa..60110a3 100755
--- a/tuner/SampleNetworkTuner/AndroidManifest.xml
+++ b/tuner/SampleNetworkTuner/AndroidManifest.xml
@@ -14,80 +14,73 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
+
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="com.android.tv.tuner.sample.network" >
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.android.tv.tuner.sample.network">
 
-    <uses-sdk
-        android:minSdkVersion="23"
-        android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="23"
+         android:targetSdkVersion="29"/>
 
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS" />
-    <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="com.android.providers.tv.permission.READ_EPG_DATA" />
-    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
+    <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="com.android.providers.tv.permission.READ_EPG_DATA"/>
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA"/>
     <!-- Permission to modify Recorded Program -->
-    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
 
     <!-- Permissions/feature for USB tuner -->
-    <uses-permission android:name="android.permission.DVB_DEVICE" />
+    <uses-permission android:name="android.permission.DVB_DEVICE"/>
 
-    <uses-feature
-        android:name="android.hardware.usb.host"
-        android:required="false" />
+    <uses-feature android:name="android.hardware.usb.host"
+         android:required="false"/>
 
     <!-- Limit only for Android TV -->
-    <uses-feature
-        android:name="android.software.leanback"
-        android:required="true" />
-    <uses-feature
-        android:name="android.software.live_tv"
-        android:required="true" />
-    <uses-feature
-        android:name="android.hardware.touchscreen"
-        android:required="false" />
+    <uses-feature android:name="android.software.leanback"
+         android:required="true"/>
+    <uses-feature android:name="android.software.live_tv"
+         android:required="true"/>
+    <uses-feature android:name="android.hardware.touchscreen"
+         android:required="false"/>
 
     <application tools:replace="android:appComponentFactory"
-        android:name="com.android.tv.tuner.sample.network.app.SampleNetworkTuner"
-        android:appComponentFactory="android.support.v4.app.CoreComponentFactory"
-        android:icon="@mipmap/ic_launcher"
-        android:label="@string/sample_network_tuner_app_name" >
+         android:name="com.android.tv.tuner.sample.network.app.SampleNetworkTuner"
+         android:appComponentFactory="android.support.v4.app.CoreComponentFactory"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/sample_network_tuner_app_name">
 
-        <activity
-            android:name="com.android.tv.tuner.sample.network.setup.SampleNetworkTunerSetupActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:exported="true"
-            android:label="@string/sample_network_tuner_app_name"
-            android:launchMode="singleInstance"
-            android:theme="@style/Theme.Setup.GuidedStep" >
+        <activity android:name="com.android.tv.tuner.sample.network.setup.SampleNetworkTunerSetupActivity"
+             android:configChanges="keyboard|keyboardHidden"
+             android:exported="true"
+             android:label="@string/sample_network_tuner_app_name"
+             android:launchMode="singleInstance"
+             android:theme="@style/Theme.Setup.GuidedStep">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
-        <service
-            android:name="com.android.tv.tuner.sample.network.tvinput.SampleNetworkTunerTvInputService"
-            android:label="@string/sample_network_tuner_app_name"
-            android:permission="android.permission.BIND_TV_INPUT"
-            android:process="com.android.tv.tuner.sample.network.tvinput" >
+        <service android:name="com.android.tv.tuner.sample.network.tvinput.SampleNetworkTunerTvInputService"
+             android:label="@string/sample_network_tuner_app_name"
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:process="com.android.tv.tuner.sample.network.tvinput"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.media.tv.TvInputService"/>
             </intent-filter>
 
-            <meta-data
-                android:name="android.media.tv.input"
-                android:resource="@xml/sample_network_tvinputservice" />
+            <meta-data android:name="android.media.tv.input"
+                 android:resource="@xml/sample_network_tvinputservice"/>
         </service>
-        <service
-            android:name="com.android.tv.tuner.tvinput.TunerStorageCleanUpService"
-            android:exported="false"
-            android:permission="android.permission.BIND_JOB_SERVICE"
-            android:process="com.android.tv.tuner" />
+        <service android:name="com.android.tv.tuner.tvinput.TunerStorageCleanUpService"
+             android:exported="false"
+             android:permission="android.permission.BIND_JOB_SERVICE"
+             android:process="com.android.tv.tuner"/>
     </application>
 
 </manifest>
diff --git a/tuner/lint-baseline.xml b/tuner/lint-baseline.xml
new file mode 100644
index 0000000..a0db5e0
--- /dev/null
+++ b/tuner/lint-baseline.xml
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 7.2.0-dev" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 26 (current min is 23): `android.app.NotificationManager#createNotificationChannel`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java"
+            line="399"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 26 (current min is 23): `new android.app.NotificationChannel`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java"
+            line="400"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 26 (current min is 23): `new android.app.Notification.Builder`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java"
+            line="406"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.app.job.JobScheduler#getPendingJob`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java"
+            line="94"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Extending RecordingSessionCompat requires API level 24 (current min is 23): `RecordingSessionCompat`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java"
+            line="39"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `RecordingSessionCompat`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java"
+            line="54"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputService.RecordingSession#notifyTuned`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java"
+            line="107"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `notifyRecordingStarted`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java"
+            line="116"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputService.RecordingSession#notifyRecordingStopped`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java"
+            line="125"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputService.RecordingSession#notifyError`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSession.java"
+            line="131"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Extending RecordingSessionCompat requires API level 24 (current min is 23): `RecordingSessionCompat`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java"
+            line="39"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `RecordingSessionCompat`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java"
+            line="54"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputService.RecordingSession#notifyTuned`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java"
+            line="107"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `notifyRecordingStarted`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java"
+            line="116"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputService.RecordingSession#notifyRecordingStopped`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java"
+            line="125"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 24 (current min is 23): `android.media.tv.TvInputService.RecordingSession#notifyError`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java"
+            line="131"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 24 (current min is 23): `android.media.tv.TvContract.RecordedPrograms#CONTENT_URI`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java"
+            line="616"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 24 (current min is 23): `android.media.tv.TvContract.RecordedPrograms#CONTENT_URI`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorker.java"
+            line="689"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 24 (current min is 23): `android.media.tv.TvContract.RecordedPrograms#CONTENT_URI`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorkerExoV2.java"
+            line="619"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 24 (current min is 23): `android.media.tv.TvContract.RecordedPrograms#CONTENT_URI`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorkerExoV2.java"
+            line="692"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 24 (current min is 23): `android.media.tv.TvContract.RecordedPrograms#CONTENT_URI`">
+        <location
+            file="packages/apps/TV/tuner/src/com/android/tv/tuner/tvinput/TunerStorageCleanUpService.java"
+            line="101"/>
+    </issue>
+
+</issues>
diff --git a/tuner/proto/Android.bp b/tuner/proto/Android.bp
index d1728a6..128eef9 100644
--- a/tuner/proto/Android.bp
+++ b/tuner/proto/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_library {
     name: "live-tv-tuner-proto",
     srcs: ["*.proto"],
diff --git a/tuner/sampletunertvinput/Android.bp b/tuner/sampletunertvinput/Android.bp
index 9d737c8..4e5900b 100644
--- a/tuner/sampletunertvinput/Android.bp
+++ b/tuner/sampletunertvinput/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_app {
     name: "sampletunertvinput",
     srcs: ["src/**/*.java"],
@@ -25,7 +29,6 @@
     platform_apis: true,
     system_ext_specific: true,
 
-
     privileged: true,
     certificate: "platform",
     // product_specific: true,
@@ -53,10 +56,19 @@
         "tv-lib-dagger-android",
         "tv-test-common",
     ],
+    optional_uses_libs: ["com.android.libraries.tv.tvsystem"],
     aaptflags: ["-0 .ts"],
     plugins: [
         "tv-auto-value",
         "tv-auto-factory",
     ],
+    required: ["com.android.tv.samples.sampletunertvinput.xml"],
     // min_sdk_version: "29",
 }
+
+prebuilt_etc {
+    name: "com.android.tv.samples.sampletunertvinput.xml",
+    sub_dir: "permissions",
+    src: "com.android.tv.samples.sampletunertvinput.xml",
+    system_ext_specific: true,
+}
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/com.android.tv.samples.sampletunertvinput.xml b/tuner/sampletunertvinput/com.android.tv.samples.sampletunertvinput.xml
new file mode 100644
index 0000000..d98e36c
--- /dev/null
+++ b/tuner/sampletunertvinput/com.android.tv.samples.sampletunertvinput.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<permissions>
+    <privapp-permissions package="com.android.tv.samples.sampletunertvinput">
+        <permission name="android.permission.ACCESS_TV_DESCRAMBLER"/>
+        <permission name="android.permission.ACCESS_TV_TUNER"/>
+        <permission name="android.permission.TUNER_RESOURCE_ACCESS"/>
+    </privapp-permissions>
+</permissions>
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..03e7965 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,40 @@
 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.MediaCodec.LinearBlock;
+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 +42,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 = false;
+    private static final String ES_FILE_NAME = "test.es";
+    private static final MediaFormat VIDEO_FORMAT;
+
+    static {
+        // format extracted for the specific input file
+        VIDEO_FORMAT = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_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 +94,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 +119,30 @@
             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();
+                mDvr = null;
+            }
+            if (mTuner != null) {
+                mTuner.close();
+                mTuner = null;
+            }
+            mDataQueue = null;
+            mSavedData = null;
         }
 
         @Override
@@ -58,7 +150,7 @@
             if (DEBUG) {
                 Log.d(TAG, "onSetSurface");
             }
-            this.surface = surface;
+            this.mSurface = surface;
             return true;
         }
 
@@ -74,20 +166,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 +185,291 @@
                 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);
+            }
+            String testFile = mContext.getFilesDir().getAbsolutePath() + "/" + ES_FILE_NAME;
+            File file = new File(testFile);
+            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(MediaFormat.MIMETYPE_VIDEO_AVC);
+                mMediaCodec.configure(VIDEO_FORMAT, mSurface, null, 0);
+            } catch (IOException e) {
+                Log.e(TAG, "Error in initCodec: " + 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);
+                        continue;
+                    }
+                    if (!mDataQueue.isEmpty()) {
+                        if (handleDataBuffer(mDataQueue.getFirst())) {
+                            // data consumed, remove.
+                            mDataQueue.pollFirst();
+                        }
+                    }
+                    if (SAVE_DATA) {
+                        mDataQueue.addAll(mSavedData);
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Error in decodeInternal: " + e.getMessage());
+            }
+        }
+
+        private boolean handleDataBuffer(MediaEvent mediaEvent) {
+            if (mediaEvent.getLinearBlock() == null) {
+                if (DEBUG) Log.d(TAG, "getLinearBlock() == null");
+                return true;
+            }
+            boolean success = false;
+            LinearBlock block = mediaEvent.getLinearBlock();
+            if (queueCodecInputBuffer(block, mediaEvent.getDataLength(), mediaEvent.getOffset(),
+                                  mediaEvent.getPts())) {
+                releaseCodecOutputBuffer();
+                success = true;
+            }
+            mediaEvent.release();
+            return success;
+        }
+
+        private boolean queueCodecInputBuffer(LinearBlock block, long sampleSize,
+                                              long offset, long pts) {
+            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 = block.map();
+                if (offset > 0 && offset < data.limit()) {
+                    data.position((int) 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()));
+                }
+                // fill codec input buffer
+                int size = sampleSize > data.limit() ? data.limit() : (int) sampleSize;
+                if (DEBUG) Log.d(TAG, "limit " + data.limit() + " sampleSize " + sampleSize);
+                if (data.hasArray()) {
+                    Log.d(TAG, "hasArray");
+                    buffer.put(data.array(), 0, size);
+                } else {
+                    byte[] array = new byte[size];
+                    data.get(array, 0, size);
+                    buffer.put(array, 0, size);
+                }
+
+                mMediaCodec.queueInputBuffer(res, 0, (int) sampleSize, pts, 0);
+            } else {
+                if (DEBUG) 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
+}
diff --git a/tuner/src/com/android/tv/tuner/data/Cea708Parser.java b/tuner/src/com/android/tv/tuner/data/Cea708Parser.java
index 7a5538c..67683cc 100644
--- a/tuner/src/com/android/tv/tuner/data/Cea708Parser.java
+++ b/tuner/src/com/android/tv/tuner/data/Cea708Parser.java
@@ -142,7 +142,7 @@
     private boolean mDtvCcPacking = false;
     private boolean mFirstServiceNumberDiscovered;
 
-    // Assign a dummy listener in order to avoid null checks.
+    // Assign an empty listener in order to avoid null checks.
     private OnCea708ParserListener mListener =
             new OnCea708ParserListener() {
                 @Override
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java b/tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
index 89b55d6..b62c186 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer/MpegTsPlayer.java
@@ -257,7 +257,7 @@
         mBuilderCallback = null;
         for (int i = 0; i < RENDERER_COUNT; i++) {
             if (renderers[i] == null) {
-                // Convert a null renderer to a dummy renderer.
+                // Convert a null renderer to an empty renderer.
                 renderers[i] = new DummyTrackRenderer();
             }
         }
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/ExoPlayerExtractorsFactory.java b/tuner/src/com/android/tv/tuner/exoplayer2/ExoPlayerExtractorsFactory.java
deleted file mode 100644
index 6af8a26..0000000
--- a/tuner/src/com/android/tv/tuner/exoplayer2/ExoPlayerExtractorsFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 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.tuner.exoplayer2;
-
-import com.google.android.exoplayer2.extractor.Extractor;
-import com.google.android.exoplayer2.extractor.ExtractorsFactory;
-import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
-import com.google.android.exoplayer2.extractor.ts.TsExtractor;
-import com.google.android.exoplayer2.util.TimestampAdjuster;
-
-/**
- * Extractor factory, mainly aim at create TsExtractor with FLAG_ALLOW_NON_IDR_KEYFRAMES flags for
- * H.264 stream
- */
-public final class ExoPlayerExtractorsFactory implements ExtractorsFactory {
-    @Override
-    public Extractor[] createExtractors() {
-        // Only create TsExtractor since we only target MPEG2TS stream.
-        Extractor[] extractors = {
-            new TsExtractor(
-                    TsExtractor.MODE_SINGLE_PMT,
-                    new TimestampAdjuster(0),
-                    new DefaultTsPayloadReaderFactory(
-                            DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES))
-        };
-        return extractors;
-    }
-}
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/ExoPlayerSampleExtractor.java b/tuner/src/com/android/tv/tuner/exoplayer2/ExoPlayerSampleExtractor.java
index 360f10c..72462f2 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer2/ExoPlayerSampleExtractor.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/ExoPlayerSampleExtractor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -20,29 +20,24 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.util.Pair;
 
-import com.android.tv.tuner.exoplayer.audio.MpegTsDefaultAudioTrackRenderer;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
-import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
-import com.android.tv.tuner.exoplayer.buffer.SimpleSampleBuffer;
+import com.android.tv.tuner.exoplayer2.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer2.buffer.MemorySampleBuffer;
+import com.android.tv.tuner.exoplayer2.buffer.PlaybackBufferListener;
+import com.android.tv.tuner.exoplayer2.buffer.RecordingSampleBuffer;
 
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
 import com.google.android.exoplayer2.C;
 import com.google.android.exoplayer2.Format;
 import com.google.android.exoplayer2.FormatHolder;
 import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.source.ExtractorMediaSource;
 import com.google.android.exoplayer2.source.MediaPeriod;
 import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
 import com.google.android.exoplayer2.source.SampleStream;
 import com.google.android.exoplayer2.source.TrackGroupArray;
 import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
@@ -65,9 +60,19 @@
  * For demux, this class relies on {@link com.google.android.exoplayer.extractor.ts.TsExtractor}.
  */
 public class ExoPlayerSampleExtractor implements SampleExtractor {
-    private static final String TAG = "ExoPlayerSampleExtracto";
+    private static final String TAG = "ExoPlayerSampleExtractor";
 
     private static final int INVALID_TRACK_INDEX = -1;
+
+    // ATSC/53 allows sample rate to be only 48Khz.
+    // One AC3 sample has 1536 frames, and its duration is 32ms.
+    private static final long AC3_SAMPLE_DURATION_US = 32000;
+    // This is around 150ms, 150ms is big enough not to under-run AudioTrack,
+    // and  150ms is also small enough to fill the buffer rapidly.
+    private static final int BUFFERED_SAMPLES_IN_AUDIOTRACK = 5;
+    private static final long INITIAL_AUDIO_BUFFERING_TIME_US =
+            BUFFERED_SAMPLES_IN_AUDIOTRACK * AC3_SAMPLE_DURATION_US;
+
     private final HandlerThread mSourceReaderThread;
     private final long mId;
 
@@ -76,17 +81,20 @@
     private BufferManager.SampleBuffer mSampleBuffer;
     private Handler mSourceReaderHandler;
     private volatile boolean mPrepared;
-    private AtomicBoolean mOnCompletionCalled = new AtomicBoolean();
+    private final AtomicBoolean mOnCompletionCalled = new AtomicBoolean();
     private IOException mExceptionOnPrepare;
-    private List<MediaFormat> mTrackFormats;
+    private List<Format> mTrackFormats;
+    private TrackGroupArray mTrackGroupArray;
     private int mVideoTrackIndex = INVALID_TRACK_INDEX;
     private boolean mVideoTrackMet;
     private long mBaseSamplePts = Long.MIN_VALUE;
-    private HashMap<Integer, Long> mLastExtractedPositionUsMap = new HashMap<>();
-    private final List<Pair<Integer, SampleHolder>> mPendingSamples = new ArrayList<>();
+    private final HashMap<Integer, Long> mLastExtractedPositionUsMap = new HashMap<>();
+    private final List<Pair<Integer, DecoderInputBuffer>> mPendingSamples = new ArrayList<>();
     private OnCompletionListener mOnCompletionListener;
     private Handler mOnCompletionListenerHandler;
     private IOException mError;
+    private MediaPeriod mMediaPeriod;
+    private Callback mCallback;
 
     /**
      * Factory for {@link ExoPlayerSampleExtractor}.
@@ -95,7 +103,7 @@
      * generated class.
      */
     public interface Factory {
-        public ExoPlayerSampleExtractor create(
+        ExoPlayerSampleExtractor create(
                 Uri uri,
                 DataSource source,
                 @Nullable BufferManager bufferManager,
@@ -117,20 +125,17 @@
                 bufferManager,
                 bufferListener,
                 isRecording,
-                Looper.myLooper(),
                 new HandlerThread("SourceReaderThread"),
                 recordingSampleBufferFactory);
     }
 
     @VisibleForTesting
-    @SuppressWarnings("MissingOverride")
-    public ExoPlayerSampleExtractor(
+    ExoPlayerSampleExtractor(
             Uri uri,
             DataSource source,
             BufferManager bufferManager,
             PlaybackBufferListener bufferListener,
             boolean isRecording,
-            Looper workerLooper,
             HandlerThread sourceReaderThread,
             RecordingSampleBuffer.Factory recordingSampleBufferFactory) {
         // It'll be used as a timeshift file chunk name's prefix.
@@ -139,12 +144,7 @@
         mSourceReaderThread = sourceReaderThread;
         mSourceReaderWorker =
                 new SourceReaderWorker(
-                        new ExtractorMediaSource(
-                                uri,
-                                /* dataSourceFactory= */ () -> source,
-                                new ExoPlayerExtractorsFactory(),
-                                new Handler(workerLooper),
-                                /* eventListener= */ error -> mError = error));
+                        new ProgressiveMediaSource.Factory(() -> source).createMediaSource(uri));
         if (isRecording) {
             mSampleBuffer =
                     recordingSampleBufferFactory.create(
@@ -154,7 +154,7 @@
                             RecordingSampleBuffer.BUFFER_REASON_RECORDING);
         } else {
             if (bufferManager == null) {
-                mSampleBuffer = new SimpleSampleBuffer(bufferListener);
+                mSampleBuffer = new MemorySampleBuffer(bufferListener);
             } else {
                 mSampleBuffer =
                         recordingSampleBufferFactory.create(
@@ -173,23 +173,22 @@
     }
 
     private class SourceReaderWorker implements Handler.Callback, MediaPeriod.Callback {
-        public static final int MSG_PREPARE = 1;
-        public static final int MSG_FETCH_SAMPLES = 2;
-        public static final int MSG_RELEASE = 3;
+        private static final int MSG_PREPARE = 1;
+        private static final int MSG_FETCH_SAMPLES = 2;
+        private static final int MSG_RELEASE = 3;
         private static final int RETRY_INTERVAL_MS = 50;
 
         private final MediaSource mSampleSource;
         private final MediaSource.SourceInfoRefreshListener mSampleSourceListener;
-        private MediaPeriod mMediaPeriod;
         private SampleStream[] mStreams;
         private boolean[] mTrackMetEos;
         private boolean mMetEos = false;
         private long mCurrentPosition;
-        private DecoderInputBuffer mDecoderInputBuffer;
-        private SampleHolder mSampleHolder;
+        private final DecoderInputBuffer mDecoderInputBuffer;
+        private final DecoderInputBuffer mDecoderInputBufferDuplicate;
         private boolean mPrepareRequested;
 
-        public SourceReaderWorker(MediaSource sampleSource) {
+        SourceReaderWorker(MediaSource sampleSource) {
             mSampleSource = sampleSource;
             mSampleSourceListener =
                     (source, timeline, manifest) -> {
@@ -199,53 +198,8 @@
             mSampleSource.prepareSource(mSampleSourceListener, null);
             mDecoderInputBuffer =
                     new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
-            mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
-        }
-
-        MediaFormat convertFormat(Format format) {
-            if (format.sampleMimeType.startsWith("audio/")) {
-                return MediaFormat.createAudioFormat(
-                        format.id,
-                        format.sampleMimeType,
-                        format.bitrate,
-                        format.maxInputSize,
-                        com.google.android.exoplayer.C.UNKNOWN_TIME_US,
-                        format.channelCount,
-                        format.sampleRate,
-                        format.initializationData,
-                        format.language,
-                        format.pcmEncoding);
-            } else if (format.sampleMimeType.startsWith("video/")) {
-                return MediaFormat.createVideoFormat(
-                        format.id,
-                        format.sampleMimeType,
-                        format.bitrate,
-                        format.maxInputSize,
-                        com.google.android.exoplayer.C.UNKNOWN_TIME_US,
-                        format.width,
-                        format.height,
-                        format.initializationData,
-                        format.rotationDegrees,
-                        format.pixelWidthHeightRatio,
-                        format.projectionData,
-                        format.stereoMode,
-                        null // colorInfo
-                        );
-            } else if (format.sampleMimeType.endsWith("/cea-608")
-                    || format.sampleMimeType.startsWith("text/")) {
-                return MediaFormat.createTextFormat(
-                        format.id,
-                        format.sampleMimeType,
-                        format.bitrate,
-                        com.google.android.exoplayer.C.UNKNOWN_TIME_US,
-                        format.language);
-            } else {
-                return MediaFormat.createFormatForMimeType(
-                        format.id,
-                        format.sampleMimeType,
-                        format.bitrate,
-                        com.google.android.exoplayer.C.UNKNOWN_TIME_US);
-            }
+            mDecoderInputBufferDuplicate =
+                    new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
         }
 
         @Override
@@ -255,6 +209,7 @@
                 return;
             }
             TrackGroupArray trackGroupArray = mMediaPeriod.getTrackGroups();
+            mTrackGroupArray = trackGroupArray;
             TrackSelection[] selections = new TrackSelection[trackGroupArray.length];
             for (int i = 0; i < selections.length; ++i) {
                 selections[i] = new FixedTrackSelection(trackGroupArray.get(i), 0);
@@ -266,7 +221,7 @@
             if (mTrackFormats == null) {
                 int trackCount = trackGroupArray.length;
                 mTrackMetEos = new boolean[trackCount];
-                List<MediaFormat> trackFormats = new ArrayList<>();
+                List<Format> trackFormats = new ArrayList<>();
                 int videoTrackCount = 0;
                 for (int i = 0; i < trackCount; i++) {
                     Format format = trackGroupArray.get(i).getFormat(0);
@@ -274,7 +229,7 @@
                         videoTrackCount++;
                         mVideoTrackIndex = i;
                     }
-                    trackFormats.add(convertFormat(format));
+                    trackFormats.add(format);
                 }
                 if (videoTrackCount > 1) {
                     // Disable dropping samples when there are multiple video tracks.
@@ -296,6 +251,7 @@
                 }
                 mSourceReaderHandler.sendEmptyMessage(MSG_FETCH_SAMPLES);
                 mPrepared = true;
+                mCallback.onPrepared();
             }
         }
 
@@ -413,35 +369,26 @@
             if (mVideoTrackIndex != INVALID_TRACK_INDEX) {
                 if (!mVideoTrackMet) {
                     if (index != mVideoTrackIndex) {
-                        SampleHolder sample =
-                                new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
-                        mSampleHolder.flags =
-                                (mDecoderInputBuffer.isKeyFrame()
-                                                ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC
-                                                : 0)
-                                        | (mDecoderInputBuffer.isDecodeOnly()
-                                                ? com.google
-                                                        .android
-                                                        .exoplayer
-                                                        .C
-                                                        .SAMPLE_FLAG_DECODE_ONLY
-                                                : 0);
+                        DecoderInputBuffer sample =
+                                new DecoderInputBuffer(
+                                        DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
+                        sample.setFlags(
+                                (mDecoderInputBuffer.isDecodeOnly() ? C.BUFFER_FLAG_DECODE_ONLY : 0)
+                                | (mDecoderInputBuffer.isEncrypted() ? C.BUFFER_FLAG_ENCRYPTED : 0)
+                                | (mDecoderInputBuffer.isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0));
                         sample.timeUs = mDecoderInputBuffer.timeUs;
-                        sample.size = mDecoderInputBuffer.data.position();
-                        sample.ensureSpaceForWrite(sample.size);
+                        int size = mDecoderInputBuffer.data.position();
+                        sample.ensureSpaceForWrite(size);
                         mDecoderInputBuffer.flip();
-                        sample.data.position(0);
+                        sample.data.position(0).limit(size);
                         sample.data.put(mDecoderInputBuffer.data);
                         sample.data.flip();
                         mPendingSamples.add(Pair.create(index, sample));
                         return;
                     }
                     mVideoTrackMet = true;
-                    mBaseSamplePts =
-                            mDecoderInputBuffer.timeUs
-                                    - MpegTsDefaultAudioTrackRenderer
-                                            .INITIAL_AUDIO_BUFFERING_TIME_US;
-                    for (Pair<Integer, SampleHolder> pair : mPendingSamples) {
+                    mBaseSamplePts = mDecoderInputBuffer.timeUs - INITIAL_AUDIO_BUFFERING_TIME_US;
+                    for (Pair<Integer, DecoderInputBuffer> pair : mPendingSamples) {
                         if (pair.second.timeUs >= mBaseSamplePts) {
                             mSampleBuffer.writeSample(pair.first, pair.second, conditionVariable);
                         }
@@ -453,28 +400,24 @@
                     }
                 }
             }
-            // Copy the decoder input to the sample holder.
-            mSampleHolder.clearData();
-            mSampleHolder.flags =
-                    (mDecoderInputBuffer.isKeyFrame()
-                                    ? com.google.android.exoplayer.C.SAMPLE_FLAG_SYNC
-                                    : 0)
-                            | (mDecoderInputBuffer.isDecodeOnly()
-                                    ? com.google.android.exoplayer.C.SAMPLE_FLAG_DECODE_ONLY
-                                    : 0);
-            mSampleHolder.timeUs = mDecoderInputBuffer.timeUs;
-            mSampleHolder.size = mDecoderInputBuffer.data.position();
-            mSampleHolder.ensureSpaceForWrite(mSampleHolder.size);
+            // Copy the decoder input buffer to pass to sample buffer.
+            mDecoderInputBufferDuplicate.clear();
+            mDecoderInputBufferDuplicate.setFlags(
+                    (mDecoderInputBuffer.isDecodeOnly() ? C.BUFFER_FLAG_DECODE_ONLY : 0)
+                            | (mDecoderInputBuffer.isEncrypted() ? C.BUFFER_FLAG_ENCRYPTED : 0)
+                            | (mDecoderInputBuffer.isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0));
+            mDecoderInputBufferDuplicate.timeUs = mDecoderInputBuffer.timeUs;
+            int size = mDecoderInputBuffer.data.position();
+            mDecoderInputBufferDuplicate.ensureSpaceForWrite(size);
             mDecoderInputBuffer.flip();
-            mSampleHolder.data.position(0);
-            mSampleHolder.data.put(mDecoderInputBuffer.data);
-            mSampleHolder.data.flip();
+            mDecoderInputBufferDuplicate.data.position(0);
+            mDecoderInputBufferDuplicate.data.put(mDecoderInputBuffer.data);
             long writeStartTimeNs = SystemClock.elapsedRealtimeNanos();
-            mSampleBuffer.writeSample(index, mSampleHolder, conditionVariable);
-
+            mSampleBuffer.writeSample(index, mDecoderInputBufferDuplicate, conditionVariable);
             // Checks whether the storage has enough bandwidth for recording samples.
             if (mSampleBuffer.isWriteSpeedSlow(
-                    mSampleHolder.size, SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) {
+                    mDecoderInputBufferDuplicate.data.position(),
+                    SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) {
                 mSampleBuffer.handleWriteSpeedSlow();
             }
         }
@@ -490,7 +433,8 @@
     }
 
     @Override
-    public boolean prepare() throws IOException {
+    public void prepare(Callback callback) throws IOException {
+        mCallback = callback;
         if (!mSourceReaderThread.isAlive()) {
             mSourceReaderThread.start();
             mSourceReaderHandler =
@@ -498,20 +442,15 @@
             mSourceReaderHandler.sendEmptyMessage(SourceReaderWorker.MSG_PREPARE);
         }
         if (mExceptionOnPrepare != null) {
-            throw mExceptionOnPrepare;
+            IOException e = mExceptionOnPrepare;
+            mExceptionOnPrepare = null;
+            throw e;
         }
-        return mPrepared;
     }
 
     @Override
-    public List<MediaFormat> getTrackFormats() {
-        return mTrackFormats;
-    }
-
-    @Override
-    public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
-        outMediaFormatHolder.format = mTrackFormats.get(track);
-        outMediaFormatHolder.drmInitData = null;
+    public void getTrackMediaFormat(int track, FormatHolder outMediaFormatHolder) {
+        outMediaFormatHolder.format = mTrackGroupArray.get(track).getFormat(0);
     }
 
     @Override
@@ -526,12 +465,17 @@
 
     @Override
     public long getBufferedPositionUs() {
-        return mSampleBuffer.getBufferedPositionUs();
+        return (mPrepared ? mMediaPeriod.getBufferedPositionUs() : 0);
     }
 
     @Override
-    public boolean continueBuffering(long positionUs) {
-        return mSampleBuffer.continueBuffering(positionUs);
+    public long getNextLoadPositionUs() {
+        return (mPrepared ? mMediaPeriod.getNextLoadPositionUs() : 0);
+    }
+
+    @Override
+    public boolean continueLoading(long positionUs) {
+        return mSampleBuffer.continueLoading(positionUs);
     }
 
     @Override
@@ -540,7 +484,7 @@
     }
 
     @Override
-    public int readSample(int track, SampleHolder sampleHolder) {
+    public int readSample(int track, DecoderInputBuffer sampleHolder) {
         return mSampleBuffer.readSample(track, sampleHolder);
     }
 
@@ -558,6 +502,11 @@
         }
     }
 
+    @Override
+    public TrackGroupArray getTrackGroups() {
+        return mTrackGroupArray;
+    }
+
     private void cleanUp() {
         boolean result = true;
         try {
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/FileSampleExtractor.java b/tuner/src/com/android/tv/tuner/exoplayer2/FileSampleExtractor.java
index c4deb58..9bd5d37 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer2/FileSampleExtractor.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/FileSampleExtractor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -16,20 +16,29 @@
 
 package com.android.tv.tuner.exoplayer2;
 
+import android.media.MediaFormat;
 import android.os.Handler;
+import android.support.annotation.Nullable;
 
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
-import com.android.tv.tuner.exoplayer.buffer.RecordingSampleBuffer;
+import com.android.tv.tuner.exoplayer2.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer2.buffer.PlaybackBufferListener;
+import com.android.tv.tuner.exoplayer2.buffer.RecordingSampleBuffer;
 
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.MediaFormatUtil;
-import com.google.android.exoplayer.SampleHolder;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.source.TrackGroup;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.util.MimeTypes;
 import com.google.auto.factory.AutoFactory;
 import com.google.auto.factory.Provided;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -40,15 +49,17 @@
 public class FileSampleExtractor implements SampleExtractor {
     private static final String TAG = "FileSampleExtractor";
     private static final boolean DEBUG = false;
+    private final long mRecordingDurationMs;
+    private IOException mOnPrepareException = null;
 
-    private int mTrackCount;
     private boolean mReleased;
-
-    private final List<MediaFormat> mTrackFormats = new ArrayList<>();
     private final BufferManager mBufferManager;
     private final PlaybackBufferListener mBufferListener;
     private BufferManager.SampleBuffer mSampleBuffer;
     private final RecordingSampleBuffer.Factory mRecordingSampleBufferFactory;
+    private TrackGroupArray mTrackGroupArray = new TrackGroupArray();
+    private final Runnable mRunnable;
+    private Callback mCallback;
 
     /**
      * Factory for {@link FileSampleExtractor}}.
@@ -57,59 +68,164 @@
      * generated class.
      */
     public interface Factory {
-        public FileSampleExtractor create(
-                BufferManager bufferManager, PlaybackBufferListener bufferListener);
+        FileSampleExtractor create(
+                BufferManager bufferManager,
+                PlaybackBufferListener bufferListener,
+                long durationMs);
     }
 
     @AutoFactory(implementing = Factory.class)
     public FileSampleExtractor(
             BufferManager bufferManager,
             PlaybackBufferListener bufferListener,
+            long durationMs,
             @Provided RecordingSampleBuffer.Factory recordingSampleBufferFactory) {
         mBufferManager = bufferManager;
         mBufferListener = bufferListener;
-        mTrackCount = -1;
         mRecordingSampleBufferFactory = recordingSampleBufferFactory;
+        mRecordingDurationMs = durationMs;
+        mRunnable = () -> {
+            try {
+                handlePrepare();
+            } catch (IOException e) {
+                mOnPrepareException = e;
+            }
+        };
     }
 
     @Override
     public void maybeThrowError() throws IOException {
-        // Do nothing.
+        if (mOnPrepareException != null) {
+            throw mOnPrepareException;
+        }
     }
 
     @Override
-    public boolean prepare() throws IOException {
+    public void prepare(Callback callback) {
+        mCallback = callback;
+        mRunnable.run();
+    }
+
+    private void handlePrepare() throws IOException {
         List<BufferManager.TrackFormat> trackFormatList = mBufferManager.readTrackInfoFiles();
         if (trackFormatList == null || trackFormatList.isEmpty()) {
             throw new IOException("Cannot find meta files for the recording.");
         }
-        mTrackCount = trackFormatList.size();
-        List<String> ids = new ArrayList<>();
-        mTrackFormats.clear();
-        for (int i = 0; i < mTrackCount; ++i) {
-            BufferManager.TrackFormat trackFormat = trackFormatList.get(i);
-            ids.add(trackFormat.trackId);
-            mTrackFormats.add(MediaFormatUtil.createMediaFormat(trackFormat.format));
+        List<Format> formats = ImmutableList.copyOf(
+                Lists.transform(trackFormatList, tf -> createFormat(tf.mediaFormat)));
+        Format videoFormat = Iterables.find(formats, f -> MimeTypes.isVideo(f.sampleMimeType));
+        Iterable<TrackGroup> captionTrackGroups = new ArrayList<>();
+        if (videoFormat != null) {
+            Format textFormat = Format.createTextSampleFormat(
+                    /* id= */ null,
+                    MimeTypes.APPLICATION_CEA708,
+                    /* selectionFlags= */ 0,
+                    videoFormat.language,
+                    /* drmInitData= */ null);
+            captionTrackGroups = ImmutableList.of(new TrackGroup(textFormat));
         }
+        Iterable<TrackGroup> trackGroups =
+                Iterables.concat(Iterables.transform(formats, TrackGroup::new), captionTrackGroups);
+        mTrackGroupArray = new TrackGroupArray(Iterables.toArray(trackGroups, TrackGroup.class));
         mSampleBuffer =
                 mRecordingSampleBufferFactory.create(
                         mBufferManager,
                         mBufferListener,
                         true,
                         RecordingSampleBuffer.BUFFER_REASON_RECORDED_PLAYBACK);
-        mSampleBuffer.init(ids, mTrackFormats);
-        return true;
+        mSampleBuffer.init(Lists.transform(trackFormatList, tf -> tf.trackId), formats);
+        mCallback.onPrepared();
+    }
+
+    private Format createFormat(MediaFormat mediaFormat) {
+        String mimeType = mediaFormat.getString(android.media.MediaFormat.KEY_MIME);
+        String language = getOptionalStringV16(mediaFormat, android.media.MediaFormat.KEY_LANGUAGE);
+        int maxInputSize =
+                getOptionalIntegerV16(mediaFormat, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
+        int width = getOptionalIntegerV16(mediaFormat, android.media.MediaFormat.KEY_WIDTH);
+        int height = getOptionalIntegerV16(mediaFormat, android.media.MediaFormat.KEY_HEIGHT);
+        int rotationDegrees = getOptionalIntegerV16(mediaFormat, "rotation-degrees");
+        int channelCount =
+                getOptionalIntegerV16(mediaFormat, android.media.MediaFormat.KEY_CHANNEL_COUNT);
+        int sampleRate =
+                getOptionalIntegerV16(mediaFormat, android.media.MediaFormat.KEY_SAMPLE_RATE);
+        ArrayList<byte[]> initializationData = new ArrayList<>();
+        for (int i = 0; mediaFormat.containsKey("csd-" + i); i++) {
+            ByteBuffer buffer = mediaFormat.getByteBuffer("csd-" + i);
+            byte[] data = new byte[buffer.limit()];
+            buffer.get(data);
+            initializationData.add(data);
+            buffer.flip();
+        }
+        long durationUs =
+                mediaFormat.containsKey(android.media.MediaFormat.KEY_DURATION)
+                        ? mediaFormat.getLong(android.media.MediaFormat.KEY_DURATION)
+                        : C.TIME_UNSET;
+        int pcmEncoding =
+                MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE;
+        if (MimeTypes.isAudio(mimeType)) {
+            return Format.createAudioSampleFormat(
+                    null,
+                    mimeType,
+                    null,
+                    Format.NO_VALUE,
+                    maxInputSize,
+                    channelCount,
+                    sampleRate,
+                    pcmEncoding,
+                    initializationData,
+                    null,
+                    0,
+                    language);
+        } else if(MimeTypes.isVideo(mimeType)) {
+            return Format.createVideoSampleFormat(
+                    null,
+                    mimeType,
+                    null,
+                    Format.NO_VALUE,
+                    maxInputSize,
+                    width,
+                    height,
+                    Format.NO_VALUE,
+                    initializationData,
+                    rotationDegrees,
+                    Format.NO_VALUE,
+                    null);
+        } else if(MimeTypes.isText(mimeType)) {
+            return Format.createTextSampleFormat(
+                    null,
+                    mimeType,
+                    null,
+                    Format.NO_VALUE,
+                    0,
+                    language,
+                    Format.NO_VALUE,
+                    null,
+                    durationUs,
+                    initializationData);
+        } else {
+            return Format.createSampleFormat(null, mimeType, durationUs);
+        }
+    }
+
+    @Nullable
+    private static String getOptionalStringV16(MediaFormat mediaFormat, String key) {
+        return mediaFormat.containsKey(key) ? mediaFormat.getString(key) : null;
+    }
+
+    private static int getOptionalIntegerV16(MediaFormat mediaFormat, String key) {
+        return mediaFormat.containsKey(key) ? mediaFormat.getInteger(key) : Format.NO_VALUE;
     }
 
     @Override
-    public List<MediaFormat> getTrackFormats() {
-        return mTrackFormats;
+    public TrackGroupArray getTrackGroups() {
+        return mTrackGroupArray;
     }
 
     @Override
-    public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
-        outMediaFormatHolder.format = mTrackFormats.get(track);
-        outMediaFormatHolder.drmInitData = null;
+    public void getTrackMediaFormat(int track, FormatHolder outMediaFormatHolder) {
+        outMediaFormatHolder.format = mTrackGroupArray.get(track).getFormat(0);
+        outMediaFormatHolder.format.copyWithDrmInitData(null);
     }
 
     @Override
@@ -138,7 +254,12 @@
 
     @Override
     public long getBufferedPositionUs() {
-        return mSampleBuffer.getBufferedPositionUs();
+        return C.msToUs(mRecordingDurationMs);
+    }
+
+    @Override
+    public long getNextLoadPositionUs() {
+        return C.TIME_END_OF_SOURCE;
     }
 
     @Override
@@ -147,13 +268,13 @@
     }
 
     @Override
-    public int readSample(int track, SampleHolder sampleHolder) {
+    public int readSample(int track, DecoderInputBuffer sampleHolder) {
         return mSampleBuffer.readSample(track, sampleHolder);
     }
 
     @Override
-    public boolean continueBuffering(long positionUs) {
-        return mSampleBuffer.continueBuffering(positionUs);
+    public boolean continueLoading(long positionUs) {
+        return mSampleBuffer.continueLoading(positionUs);
     }
 
     @Override
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsMediaPeriod.java b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsMediaPeriod.java
new file mode 100644
index 0000000..733b8f8
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsMediaPeriod.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2019 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.tuner.exoplayer2;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.SeekParameters;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.source.MediaPeriod;
+import com.google.android.exoplayer2.source.SampleStream;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.trackselection.TrackSelection;
+import com.google.android.exoplayer2.util.Assertions;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** A {@link MediaPeriod} that extracts data using an {@link SampleExtractor}. */
+/* package */ final class MpegTsMediaPeriod implements MediaPeriod, SampleExtractor.Callback {
+
+    private static final int TRACK_STATE_DISABLED = 0;
+    private static final int TRACK_STATE_ENABLED = 1;
+    private static final int TRACK_STATE_FORMAT_SENT = 2;
+
+    private final SampleExtractor mExtractor;
+    private final ArrayList<SampleStreamImpl> mSampleStreams = new ArrayList<>();
+    private final List<Integer> mTrackStates = new ArrayList<>();
+    private final List<Boolean> mPendingDiscontinuities = new ArrayList<>();
+
+    private boolean mPrepared;
+    private long mLastSeekPositionUs;
+    private long mPendingSeekPositionUs;
+    private Callback mCallback;
+    private IOException mExceptionOnPrepare;
+
+    public MpegTsMediaPeriod(SampleExtractor extractor) {
+        this.mExtractor = extractor;
+    }
+
+    @Override
+    public void prepare(Callback callback, long positionUs) {
+        mCallback = callback;
+        try {
+            mExtractor.prepare(this);
+        } catch (IOException e) {
+            mExceptionOnPrepare = e;
+        }
+    }
+
+    private void enable(int track) {
+        Assertions.checkState(mPrepared);
+        Assertions.checkState(mTrackStates.get(track) == TRACK_STATE_DISABLED);
+        mTrackStates.set(track, TRACK_STATE_ENABLED);
+        mExtractor.selectTrack(track);
+    }
+
+    private void disable(int track) {
+        Assertions.checkState(mPrepared);
+        Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED);
+        mExtractor.deselectTrack(track);
+        mPendingDiscontinuities.set(track, false);
+        mTrackStates.set(track, TRACK_STATE_DISABLED);
+    }
+
+    @Override
+    public void maybeThrowPrepareError() throws IOException {
+        if (mExceptionOnPrepare != null) {
+            IOException e = mExceptionOnPrepare;
+            mExceptionOnPrepare = null;
+            throw e;
+        }
+    }
+
+    @Override
+    public TrackGroupArray getTrackGroups() {
+        return mExtractor.getTrackGroups();
+    }
+
+    @Override
+    public long selectTracks(
+            TrackSelection[] selections,
+            boolean[] mayRetainStreamFlags,
+            SampleStream[] streams,
+            boolean[] streamResetFlags,
+            long positionUs) {
+        TrackGroupArray trackGroups = mExtractor.getTrackGroups();
+        for (int i = 0; i < selections.length; i++) {
+            if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
+                SampleStreamImpl stream = (SampleStreamImpl) streams[i];
+                disable(stream.mIndex);
+                mSampleStreams.remove(stream);
+                streams[i] = null;
+            }
+            if (streams[i] == null && selections[i] != null) {
+                int index = trackGroups.indexOf(selections[i].getTrackGroup());
+                SampleStreamImpl stream = new SampleStreamImpl(index);
+                mSampleStreams.add(stream);
+                streams[i] = stream;
+                streamResetFlags[i] = true;
+                enable(index);
+            }
+        }
+        seekToUsInternal(positionUs, positionUs != 0);
+        return positionUs;
+    }
+
+    @Override
+    public void discardBuffer(long positionUs, boolean toKeyframe) {
+        // Handled by extractor
+    }
+
+    @Override
+    public void reevaluateBuffer(long positionUs) {
+        // Do nothing.
+    }
+
+    @Override
+    public boolean continueLoading(long positionUs) {
+        return mExtractor.continueLoading(positionUs);
+    }
+
+    @Override
+    public long readDiscontinuity() {
+        boolean notifyDiscontinuity = false;
+        for (int i = 0; i < mPendingDiscontinuities.size(); i++) {
+            if (mPendingDiscontinuities.get(i)) {
+                mPendingDiscontinuities.set(i, false);
+                notifyDiscontinuity = true;
+            }
+        }
+        return (notifyDiscontinuity ? mLastSeekPositionUs : C.TIME_UNSET);
+    }
+
+    @Override
+    public long getNextLoadPositionUs() {
+        return mExtractor.getNextLoadPositionUs();
+    }
+
+    @Override
+    public long getBufferedPositionUs() {
+        return mExtractor.getBufferedPositionUs();
+    }
+
+    @Override
+    public long seekToUs(long positionUs) {
+        for (int i = 0; i < mSampleStreams.size(); i++) {
+            mSampleStreams.get(i).reset();
+        }
+        seekToUsInternal(positionUs, false);
+        return positionUs;
+    }
+
+    private void seekToUsInternal(long positionUs, boolean force) {
+        // Unless forced, avoid duplicate calls to the underlying extractor's seek method
+        // in the case that there have been no interleaving calls to readSample.
+        if (force || mPendingSeekPositionUs != positionUs) {
+            mLastSeekPositionUs = positionUs;
+            mPendingSeekPositionUs = positionUs;
+            mExtractor.seekTo(positionUs);
+            for (int i = 0; i < mTrackStates.size(); ++i) {
+                if (mTrackStates.get(i) != TRACK_STATE_DISABLED) {
+                    mPendingDiscontinuities.set(i, true);
+                }
+            }
+        }
+    }
+
+    @Override
+    public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
+        return positionUs;
+    }
+
+    @Override
+    public void onPrepared() {
+        mPrepared = true;
+        int trackCount = mExtractor.getTrackGroups().length;
+        mTrackStates.clear();
+        mPendingDiscontinuities.clear();
+        for (int i = 0; i < trackCount; ++i) {
+            mTrackStates.add(i, TRACK_STATE_DISABLED);
+            mPendingDiscontinuities.add(i, false);
+        }
+        mCallback.onPrepared(this);
+    }
+
+    public void release() {
+        mExtractor.release();
+    }
+
+    private final class SampleStreamImpl implements SampleStream {
+
+        private static final int STREAM_STATE_SEND_FORMAT = 0;
+        private static final int STREAM_STATE_SEND_SAMPLE = 1;
+        private static final int STREAM_STATE_END_OF_STREAM = 2;
+        private final int mIndex;
+
+        private int streamState;
+
+        SampleStreamImpl(int index) {
+            mIndex = index;
+        }
+
+        void reset() {
+            if (streamState == STREAM_STATE_END_OF_STREAM) {
+                streamState = STREAM_STATE_SEND_SAMPLE;
+            }
+        }
+
+        @Override
+        public boolean isReady() {
+            return true;
+        }
+
+        @Override
+        public void maybeThrowError() throws IOException {
+            mExtractor.maybeThrowError();
+        }
+
+        @Override
+        public int readData(
+                FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
+            Assertions.checkState(mPrepared);
+            Assertions.checkState(mTrackStates.get(mIndex) != TRACK_STATE_DISABLED);
+            if (mPendingDiscontinuities.get(mIndex)) {
+                return C.RESULT_NOTHING_READ;
+            }
+            if (requireFormat || mTrackStates.get(mIndex) != TRACK_STATE_FORMAT_SENT) {
+                mExtractor.getTrackMediaFormat(mIndex, formatHolder);
+                mTrackStates.set(mIndex, TRACK_STATE_FORMAT_SENT);
+                return C.RESULT_FORMAT_READ;
+            }
+            mPendingSeekPositionUs = C.TIME_UNSET;
+            return mExtractor.readSample(mIndex, buffer);
+        }
+
+        @Override
+        public int skipData(long positionUs) {
+            if (positionUs > 0 && streamState != STREAM_STATE_END_OF_STREAM) {
+                streamState = STREAM_STATE_END_OF_STREAM;
+                return 1;
+            }
+            return 0;
+        }
+    }
+}
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsMediaSource.java b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsMediaSource.java
new file mode 100644
index 0000000..a9ab43b
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsMediaSource.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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.tuner.exoplayer2;
+
+import android.support.annotation.Nullable;
+
+import com.google.android.exoplayer2.source.BaseMediaSource;
+import com.google.android.exoplayer2.source.MediaPeriod;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.SinglePeriodTimeline;
+import com.google.android.exoplayer2.upstream.Allocator;
+import com.google.android.exoplayer2.upstream.TransferListener;
+import com.google.android.exoplayer2.util.Assertions;
+
+/** {@link MediaSource} that extracts sample data using a {@link SampleExtractor}. */
+
+public final class MpegTsMediaSource extends BaseMediaSource {
+
+    private static final String TAG = "MpegTsMediaSource";
+
+    private final SampleExtractor mSampleExtractor;
+
+    /**
+     * Creates a new sample source that extracts samples using {@code mSampleExtractor}.
+     *
+     * @param sampleExtractor a sample extractor for accessing media samples
+     */
+    public MpegTsMediaSource(SampleExtractor sampleExtractor) {
+        mSampleExtractor = Assertions.checkNotNull(sampleExtractor);
+    }
+
+    @Override
+    protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
+        refreshSourceInfo(new SinglePeriodTimeline(0, false, false), null);
+    }
+
+    @Override
+    protected void releaseSourceInternal() {
+        // Do nothing
+    }
+
+    @Override
+    public void maybeThrowSourceInfoRefreshError() {
+        // Do nothing
+    }
+
+    @Override
+    public MpegTsMediaPeriod createPeriod(
+            MediaPeriodId id,
+            Allocator allocator,
+            long startPositionUs) {
+        return new MpegTsMediaPeriod(mSampleExtractor);
+    }
+
+    @Override
+    public void releasePeriod(MediaPeriod mediaPeriod) {
+        ((MpegTsMediaPeriod) mediaPeriod).release();
+    }
+}
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java
index 4fa44c9..7cb4b9b 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsPlayerV2.java
@@ -17,20 +17,14 @@
 package com.android.tv.tuner.exoplayer2;
 
 import android.content.Context;
-import android.media.PlaybackParams;
-import android.net.Uri;
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 import android.view.Surface;
 
-import com.android.tv.common.SoftPreconditions;
 import com.android.tv.tuner.data.Cea708Data;
 import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
 import com.android.tv.tuner.data.Cea708Parser;
-import com.android.tv.tuner.data.TunerChannel;
 import com.android.tv.tuner.source.TsDataSource;
-import com.android.tv.tuner.source.TsDataSourceManager;
-import com.android.tv.tuner.ts.EventDetector;
 import com.android.tv.tuner.tvinput.debug.TunerDebug;
 import com.google.android.exoplayer2.C;
 import com.google.android.exoplayer2.ExoPlaybackException;
@@ -42,7 +36,6 @@
 import com.google.android.exoplayer2.Timeline;
 import com.google.android.exoplayer2.audio.AudioListener;
 import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
 import com.google.android.exoplayer2.source.TrackGroup;
 import com.google.android.exoplayer2.source.TrackGroupArray;
 import com.google.android.exoplayer2.text.Cue;
@@ -96,12 +89,8 @@
         /** Notifies the caption event. */
         void onEmitCaptionEvent(CaptionEvent event);
 
-        /** Notifies clearing up whole closed caption event. */
-        void onClearCaptionEvent();
-
         /** Notifies the discovered caption service number. */
         void onDiscoverCaptionServiceNumber(int serviceNumber);
-
     }
 
     public static final int MIN_BUFFER_MS = 0;
@@ -124,33 +113,27 @@
     public static final int STATE_READY = Player.STATE_READY;
     public static final int STATE_ENDED = Player.STATE_ENDED;
 
-    private static final float MAX_SMOOTH_TRICKPLAY_SPEED = 9.0f;
-    private static final float MIN_SMOOTH_TRICKPLAY_SPEED = 0.1f;
-
-    private int mCaptionServiceNumber = Cea708Data.EMPTY_SERVICE_NUMBER;
-
-    private final Context mContext;
     private final SimpleExoPlayer mPlayer;
     private final DefaultTrackSelector mTrackSelector;
-    private final TsDataSourceManager mSourceManager;
 
     private DefaultTrackSelector.Parameters mTrackSelectorParameters;
     private TrackGroupArray mLastSeenTrackGroupArray;
+    private TrackSelectionArray mLastSeenTrackSelections;
     private Callback mCallback;
     private TsDataSource mDataSource;
     private VideoEventListener mVideoEventListener;
-    private boolean mTrickplayRunning;
+    private boolean mCaptionsAvailable = false;
 
     /**
      * Creates MPEG2-TS stream player.
      *
      * @param context       the application context
-     * @param sourceManager the manager for {@link TsDataSource}
      * @param callback      callback for playback state changes
      */
-    public MpegTsPlayerV2(Context context, TsDataSourceManager sourceManager, Callback callback) {
-        mContext = context;
-        mTrackSelectorParameters = new DefaultTrackSelector.ParametersBuilder().build();
+    public MpegTsPlayerV2(Context context, Callback callback) {
+        mTrackSelectorParameters = new DefaultTrackSelector.ParametersBuilder()
+                                           .setSelectUndeterminedTextLanguage(true)
+                                           .build();
         mTrackSelector = new DefaultTrackSelector();
         mTrackSelector.setParameters(mTrackSelectorParameters);
         mLastSeenTrackGroupArray = null;
@@ -159,7 +142,6 @@
         mPlayer.addVideoListener(this);
         mPlayer.addAudioListener(this);
         mPlayer.addTextOutput(this);
-        mSourceManager = sourceManager;
         mCallback = callback;
     }
 
@@ -178,7 +160,6 @@
      * @param captionServiceNumber the service number of CEA-708 closed caption
      */
     public void setCaptionServiceNumber(int captionServiceNumber) {
-        mCaptionServiceNumber = captionServiceNumber;
         if (captionServiceNumber == Cea708Data.EMPTY_SERVICE_NUMBER) return;
         MappedTrackInfo mappedTrackInfo = mTrackSelector.getCurrentMappedTrackInfo();
         if (mappedTrackInfo != null) {
@@ -207,6 +188,10 @@
      */
     @Override
     public void onCues(List<Cue> cues) {
+        if (!mCaptionsAvailable && cues != null && cues.size() != 0) {
+            mCaptionsAvailable = true;
+            onTracksChanged(mLastSeenTrackGroupArray, mLastSeenTrackSelections);
+        }
         mVideoEventListener.onEmitCaptionEvent(
                 new CaptionEvent(
                         Cea708Parser.CAPTION_EMIT_TYPE_COMMAND_DFX,
@@ -225,8 +210,7 @@
                                 /* penStyle= */ 0,
                                 /* windowStyle= */ 2)));
         mVideoEventListener.onEmitCaptionEvent(
-                new CaptionEvent(Cea708Parser.CAPTION_EMIT_TYPE_BUFFER,
-                        cues));
+                new CaptionEvent(Cea708Parser.CAPTION_EMIT_TYPE_BUFFER, cues));
     }
 
     /**
@@ -239,26 +223,13 @@
     }
 
     /**
-     * Creates renderers and {@link TsDataSource} and initializes player.
-     *
-     * @return true when everything is created and initialized well, false otherwise
+     * Prepares player.
      */
-    public boolean prepare(TunerChannel channel, EventDetector.EventListener eventListener) {
-        TsDataSource source = null;
-        if (channel != null) {
-            source = mSourceManager.createDataSource(mContext, channel, eventListener);
-            if (source == null) {
-                return false;
-            }
-        }
-        mDataSource = source;
-        MediaSource mediaSource =
-                new ProgressiveMediaSource.Factory(() -> mDataSource).createMediaSource(Uri.EMPTY);
-        mPlayer.prepare(mediaSource, true, false);
-        return true;
+    public void prepare(TsDataSource dataSource, MediaSource mediaSource) {
+        mDataSource = dataSource;
+        mPlayer.prepare(mediaSource, false, false);
     }
 
-
     /** Returns {@link TsDataSource} which provides MPEG2-TS stream. */
     public TsDataSource getDataSource() {
         return mDataSource;
@@ -272,28 +243,6 @@
      */
     public void setPlayWhenReady(boolean playWhenReady) {
         mPlayer.setPlayWhenReady(playWhenReady);
-        stopSmoothTrickplay(false);
-    }
-
-    /** Returns true, if trickplay is supported. */
-    public boolean supportSmoothTrickPlay(float playbackSpeed) {
-        return playbackSpeed > MIN_SMOOTH_TRICKPLAY_SPEED
-                       && playbackSpeed < MAX_SMOOTH_TRICKPLAY_SPEED;
-    }
-
-    /**
-     * Starts trickplay. It'll be reset, if {@link #seekTo} or {@link #setPlayWhenReady} is called.
-     */
-    public void startSmoothTrickplay(PlaybackParams playbackParams) {
-        SoftPreconditions.checkState(supportSmoothTrickPlay(playbackParams.getSpeed()));
-        mPlayer.setPlayWhenReady(true);
-        mTrickplayRunning = true;
-    }
-
-    private void stopSmoothTrickplay(boolean calledBySeek) {
-        if (mTrickplayRunning) {
-            mTrickplayRunning = false;
-        }
     }
 
     /**
@@ -303,7 +252,6 @@
      */
     public void seekTo(long positionMs) {
         mPlayer.seekTo(positionMs);
-        stopSmoothTrickplay(true);
     }
 
     /** Releases the player. */
@@ -311,6 +259,7 @@
         if (mDataSource != null) {
             mDataSource = null;
         }
+        mCaptionsAvailable = false;
         mCallback = null;
         mPlayer.release();
     }
@@ -358,7 +307,9 @@
      *
      * @param enable enables the audio and closed caption when {@code true}, disables otherwise.
      */
-    public void setAudioTrackAndClosedCaption(boolean enable) {}
+    public void setAudioTrackAndClosedCaption(boolean enable) {
+        //TODO Add handling to enable/disable audio and captions
+    }
 
     @Override
     public void onTimelineChanged(
@@ -369,9 +320,19 @@
     @Override
     public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
         if (trackGroups != mLastSeenTrackGroupArray) {
+            MappedTrackInfo mappedTrackInfo = mTrackSelector.getCurrentMappedTrackInfo();
+            if (mCallback != null
+                    && mappedTrackInfo != null
+                    && mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO)
+                    == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
+                mCallback.onAudioUnplayable();
+            }
             mLastSeenTrackGroupArray = trackGroups;
         }
-        if (mVideoEventListener != null) {
+        if (trackSelections != mLastSeenTrackSelections) {
+            mLastSeenTrackSelections = trackSelections;
+        }
+        if (mVideoEventListener != null && mCaptionsAvailable) {
             MappedTrackInfo mappedTrackInfo = mTrackSelector.getCurrentMappedTrackInfo();
             if (mappedTrackInfo != null) {
                 int rendererCount = mappedTrackInfo.getRendererCount();
@@ -523,7 +484,7 @@
     @Override
     public void onDroppedFrames(int count, long elapsed) {
         TunerDebug.notifyVideoFrameDrop(count, elapsed);
-        if (mTrickplayRunning && mCallback != null) {
+        if (mCallback != null) {
             mCallback.onSmoothTrickplayForceStopped();
         }
     }
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsSampleExtractor.java b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsSampleExtractor.java
index 544e189..d6640b5 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsSampleExtractor.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/MpegTsSampleExtractor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -16,20 +16,22 @@
 
 package com.android.tv.tuner.exoplayer2;
 
+import android.media.MediaFormat;
 import android.net.Uri;
 import android.os.Handler;
 import android.support.annotation.Nullable;
 
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
-import com.android.tv.tuner.exoplayer.buffer.SamplePool;
+import com.android.tv.tuner.exoplayer2.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer2.buffer.InputBufferPool;
+import com.android.tv.tuner.exoplayer2.buffer.PlaybackBufferListener;
 
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.util.MimeTypes;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.source.TrackGroupArray;
 import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.util.MimeTypes;
 import com.google.auto.factory.AutoFactory;
 import com.google.auto.factory.Provided;
 
@@ -39,23 +41,27 @@
 import java.util.LinkedList;
 import java.util.List;
 
-/** Extracts samples from {@link DataSource} for MPEG-TS streams. */
-public final class MpegTsSampleExtractor implements SampleExtractor {
-    public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
+/**
+ * Extracts samples from {@link DataSource} for MPEG-TS streams.
+ * Managed captions for live and recorded playback since exoplayer earlier version needed it.
+ * //TODO: Can be discarded from exoplayer2
+ */
+public final class MpegTsSampleExtractor implements SampleExtractor, SampleExtractor.Callback {
 
     private static final int CC_BUFFER_SIZE_IN_BYTES = 9600 / 8;
 
     private final SampleExtractor mSampleExtractor;
-    private final List<MediaFormat> mTrackFormats = new ArrayList<>();
+    private final List<Format> mTrackFormats = new ArrayList<>();
     private final List<Boolean> mReachedEos = new ArrayList<>();
     private int mVideoTrackIndex;
-    private final SamplePool mCcSamplePool = new SamplePool();
-    private final List<SampleHolder> mPendingCcSamples = new LinkedList<>();
+    private final InputBufferPool mCcInputBufferPool = new InputBufferPool();
+    private final List<DecoderInputBuffer> mPendingCcSamples = new LinkedList<>();
 
     private int mCea708TextTrackIndex;
     private boolean mCea708TextTrackSelected;
 
     private CcParser mCcParser;
+    private Callback mCallback;
 
     private void init() {
         mVideoTrackIndex = -1;
@@ -70,10 +76,12 @@
      * generated class.
      */
     public interface Factory {
-        public MpegTsSampleExtractor create(
-                BufferManager bufferManager, PlaybackBufferListener bufferListener);
+        MpegTsSampleExtractor create(
+                BufferManager bufferManager,
+                PlaybackBufferListener bufferListener,
+                long durationMs);
 
-        public MpegTsSampleExtractor create(
+        MpegTsSampleExtractor create(
                 DataSource source,
                 @Nullable BufferManager bufferManager,
                 PlaybackBufferListener bufferListener);
@@ -104,13 +112,16 @@
      * @param bufferManager the samples provider which is stored in physical storage
      * @param bufferListener the {@link PlaybackBufferListener} to notify buffer storage status
      *     change
+     * @param durationMs the duration of recording in Milliseconds
      */
     @AutoFactory(implementing = Factory.class)
     public MpegTsSampleExtractor(
             BufferManager bufferManager,
             PlaybackBufferListener bufferListener,
+            long durationMs,
             @Provided FileSampleExtractor.Factory fileSampleExtractorFactory) {
-        mSampleExtractor = fileSampleExtractorFactory.create(bufferManager, bufferListener);
+        mSampleExtractor =
+                fileSampleExtractorFactory.create(bufferManager, bufferListener, durationMs);
         init();
     }
 
@@ -122,43 +133,14 @@
     }
 
     @Override
-    public boolean prepare() throws IOException {
-        if (!mSampleExtractor.prepare()) {
-            return false;
-        }
-        List<MediaFormat> formats = mSampleExtractor.getTrackFormats();
-        int trackCount = formats.size();
-        mTrackFormats.clear();
-        mReachedEos.clear();
-
-        for (int i = 0; i < trackCount; ++i) {
-            mTrackFormats.add(formats.get(i));
-            mReachedEos.add(false);
-            String mime = formats.get(i).mimeType;
-            if (MimeTypes.isVideo(mime) && mVideoTrackIndex == -1) {
-                mVideoTrackIndex = i;
-                if (android.media.MediaFormat.MIMETYPE_VIDEO_MPEG2.equals(mime)) {
-                    mCcParser = new Mpeg2CcParser();
-                } else if (android.media.MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
-                    mCcParser = new H264CcParser();
-                }
-            }
-        }
-
-        if (mVideoTrackIndex != -1) {
-            mCea708TextTrackIndex = trackCount;
-        }
-        if (mCea708TextTrackIndex >= 0) {
-            mTrackFormats.add(
-                    MediaFormat.createTextFormat(
-                            null, MIMETYPE_TEXT_CEA_708, 0, mTrackFormats.get(0).durationUs, ""));
-        }
-        return true;
+    public void prepare(Callback callback) throws IOException {
+        mCallback = callback;
+        mSampleExtractor.prepare(this);
     }
 
     @Override
-    public List<MediaFormat> getTrackFormats() {
-        return mTrackFormats;
+    public TrackGroupArray getTrackGroups() {
+        return mSampleExtractor.getTrackGroups();
     }
 
     @Override
@@ -185,46 +167,51 @@
     }
 
     @Override
+    public long getNextLoadPositionUs() {
+        return mSampleExtractor.getNextLoadPositionUs();
+    }
+
+    @Override
     public void seekTo(long positionUs) {
         mSampleExtractor.seekTo(positionUs);
-        for (SampleHolder holder : mPendingCcSamples) {
-            mCcSamplePool.releaseSample(holder);
+        for (DecoderInputBuffer holder : mPendingCcSamples) {
+            mCcInputBufferPool.releaseSample(holder);
         }
         mPendingCcSamples.clear();
     }
 
     @Override
-    public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
-        if (track != mCea708TextTrackIndex) {
-            mSampleExtractor.getTrackMediaFormat(track, outMediaFormatHolder);
-        }
+    public void getTrackMediaFormat(int track, FormatHolder outMediaFormatHolder) {
+        mSampleExtractor.getTrackMediaFormat(track, outMediaFormatHolder);
     }
 
     @Override
-    public int readSample(int track, SampleHolder sampleHolder) {
+    public int readSample(int track, DecoderInputBuffer sampleHolder) {
         if (track == mCea708TextTrackIndex) {
             if (mCea708TextTrackSelected && !mPendingCcSamples.isEmpty()) {
-                SampleHolder holder = mPendingCcSamples.remove(0);
+                DecoderInputBuffer holder = mPendingCcSamples.remove(0);
+                sampleHolder.ensureSpaceForWrite(CC_BUFFER_SIZE_IN_BYTES);
                 holder.data.flip();
                 sampleHolder.timeUs = holder.timeUs;
+                sampleHolder.data.clear();
                 sampleHolder.data.put(holder.data);
-                mCcSamplePool.releaseSample(holder);
-                return SampleSource.SAMPLE_READ;
+                mCcInputBufferPool.releaseSample(holder);
+                return C.RESULT_BUFFER_READ;
             } else {
                 return mVideoTrackIndex < 0 || mReachedEos.get(mVideoTrackIndex)
-                        ? SampleSource.END_OF_STREAM
-                        : SampleSource.NOTHING_READ;
+                        ? C.RESULT_END_OF_INPUT
+                        : C.RESULT_NOTHING_READ;
             }
         }
 
         int result = mSampleExtractor.readSample(track, sampleHolder);
         switch (result) {
-            case SampleSource.END_OF_STREAM:
+            case C.RESULT_END_OF_INPUT:
                 {
                     mReachedEos.set(track, true);
                     break;
                 }
-            case SampleSource.SAMPLE_READ:
+            case C.RESULT_BUFFER_READ:
                 {
                     if (mCea708TextTrackSelected
                             && track == mVideoTrackIndex
@@ -246,21 +233,46 @@
     }
 
     @Override
-    public boolean continueBuffering(long positionUs) {
-        return mSampleExtractor.continueBuffering(positionUs);
+    public boolean continueLoading(long positionUs) {
+        return mSampleExtractor.continueLoading(positionUs);
     }
 
     @Override
     public void setOnCompletionListener(OnCompletionListener listener, Handler handler) {}
 
+    @Override
+    public void onPrepared() {
+        int trackCount = mSampleExtractor.getTrackGroups().length;
+        mTrackFormats.clear();
+        mReachedEos.clear();
+
+        for (int i = 0; i < trackCount; ++i) {
+            Format format = mSampleExtractor.getTrackGroups().get(i).getFormat(0);
+            mTrackFormats.add(format);
+            mReachedEos.add(false);
+            String mime = format.sampleMimeType;
+            if (MimeTypes.isVideo(mime) && mVideoTrackIndex == -1) {
+                mVideoTrackIndex = i;
+                if (MediaFormat.MIMETYPE_VIDEO_MPEG2.equals(mime)) {
+                    mCcParser = new Mpeg2CcParser();
+                } else if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
+                    mCcParser = new H264CcParser();
+                }
+            } else if (MimeTypes.APPLICATION_CEA708.equals(mime)) {
+                mCea708TextTrackIndex = i;
+            }
+        }
+        mCallback.onPrepared();
+    }
+
     private abstract class CcParser {
         // Interim buffer for reduce direct access to ByteBuffer which is expensive. Using
         // relatively small buffer size in order to minimize memory footprint increase.
-        protected final byte[] mBuffer = new byte[1024];
+        final byte[] mBuffer = new byte[1024];
 
         abstract void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs);
 
-        protected int parseClosedCaption(ByteBuffer buffer, int offset, long presentationTimeUs) {
+        int parseClosedCaption(ByteBuffer buffer, int offset, long presentationTimeUs) {
             // For the details of user_data_type_structure, see ATSC A/53 Part 4 - Table 6.9.
             int pos = offset;
             if (pos + 2 >= buffer.position()) {
@@ -272,7 +284,7 @@
             if (!processCcDataFlag || pos + 3 * ccCount >= buffer.position() || ccCount == 0) {
                 return offset;
             }
-            SampleHolder holder = mCcSamplePool.acquireSample(CC_BUFFER_SIZE_IN_BYTES);
+            DecoderInputBuffer holder = mCcInputBufferPool.acquireSample(CC_BUFFER_SIZE_IN_BYTES);
             for (int i = 0; i < 3 * ccCount; i++) {
                 holder.data.put(buffer.get(pos++));
             }
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/SampleExtractor.java b/tuner/src/com/android/tv/tuner/exoplayer2/SampleExtractor.java
index c3040e6..dde44a7 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer2/SampleExtractor.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/SampleExtractor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -16,19 +16,20 @@
 package com.android.tv.tuner.exoplayer2;
 
 import android.os.Handler;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.FormatHolder;
+import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+
 import java.io.IOException;
-import java.util.List;
 
 /**
  * Extractor for reading track metadata and samples stored in tracks.
  *
  * <p>Call {@link #prepare} until it returns {@code true}, then access track metadata via {@link
- * #getTrackFormats} and {@link #getTrackMediaFormat}.
+ * #getTrackGroups and {@link #getTrackMediaFormat}.
  *
  * <p>Pass indices of tracks to read from to {@link #selectTrack}. A track can later be deselected
  * by calling {@link #deselectTrack}. It is safe to select/deselect tracks after reading sample data
@@ -36,6 +37,7 @@
  *
  * <p>Call {@link #release()} when the extractor is no longer needed to free resources.
  */
+// TODO: Should be replaced by {@link com.google.android.exoplayer2.source.MediaPeriod}.
 public interface SampleExtractor {
 
     /**
@@ -49,13 +51,14 @@
     /**
      * Prepares the extractor for reading track metadata and samples.
      *
-     * @return whether the source is ready; if {@code false}, this method must be called again.
+     * @param callback Callback to receive updates from this sample extractor, including being
+     *                 notified when preparation completes.
      * @throws IOException thrown if the source can't be read
      */
-    boolean prepare() throws IOException;
+    void prepare(Callback callback) throws IOException;
 
     /** Returns track information about all tracks that can be selected. */
-    List<MediaFormat> getTrackFormats();
+    TrackGroupArray getTrackGroups();
 
     /** Selects the track at {@code index} for reading sample data. */
     void selectTrack(int index);
@@ -69,12 +72,20 @@
      * <p>This method should not be called until after the extractor has been successfully prepared.
      *
      * @return an estimate of the absolute position in microseconds up to which data is buffered, or
-     *     {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or
-     *     {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available.
+     *     {@link C#TIME_END_OF_SOURCE} if data is buffered to the end of the stream, or
+     *     {@link C#TIME_UNSET} if no estimate is available.
      */
     long getBufferedPositionUs();
 
     /**
+     * Returns the next load time, or {@link C#TIME_END_OF_SOURCE} if loading has finished.
+     *
+     * <p>This method is only called after the period has been prepared. It may be called when no
+     * tracks are selected.
+     */
+    long getNextLoadPositionUs();
+
+    /**
      * Seeks to the specified time in microseconds.
      *
      * <p>This method should not be called until after the extractor has been successfully prepared.
@@ -83,30 +94,30 @@
      */
     void seekTo(long positionUs);
 
-    /** Stores the {@link MediaFormat} of {@code track}. */
-    void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder);
+    /** Stores the {@link Format} of {@code track}. */
+    void getTrackMediaFormat(int track, FormatHolder outMediaFormatHolder);
 
     /**
      * Reads the next sample in the track at index {@code track} into {@code sampleHolder},
-     * returning {@link SampleSource#SAMPLE_READ} if it is available.
+     * returning {@link C#RESULT_BUFFER_READ} if it is available.
      *
      * <p>Advances to the next sample if a sample was read.
      *
      * @param track the index of the track from which to read a sample
-     * @param sampleHolder the holder for read sample data, if {@link SampleSource#SAMPLE_READ} is
+     * @param sampleHolder the holder for read sample data, if {@link C#RESULT_BUFFER_READ} is
      *     returned
-     * @return {@link SampleSource#SAMPLE_READ} if a sample was read into {@code sampleHolder}, or
-     *     {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or
-     *     {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not
+     * @return {@link C#RESULT_BUFFER_READ} if a sample was read into {@code sampleHolder}, or
+     *     {@link C#RESULT_END_OF_INPUT} if the last samples in all tracks have been read, or
+     *     {@link C#RESULT_NOTHING_READ} if the sample cannot be read immediately as it is not
      *     loaded.
      */
-    int readSample(int track, SampleHolder sampleHolder);
+    int readSample(int track, DecoderInputBuffer sampleHolder);
 
     /** Releases resources associated with this extractor. */
     void release();
 
     /** Indicates to the source that it should still be buffering data. */
-    boolean continueBuffering(long positionUs);
+    boolean continueLoading(long positionUs);
 
     /**
      * Sets OnCompletionListener for notifying the completion of SampleExtractor.
@@ -128,4 +139,11 @@
          */
         void onCompletion(boolean result, long lastExtractedPositionUs);
     }
+
+    /** A callback to be notified of {@link SampleExtractor} events. */
+    interface Callback {
+
+        /** Called when preparation completes. */
+        void onPrepared();
+    }
 }
diff --git a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java
index 5a3f668..93fc055 100644
--- a/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java
+++ b/tuner/src/com/android/tv/tuner/exoplayer2/buffer/SampleChunkIoHelper.java
@@ -259,8 +259,11 @@
         mediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
         mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
         mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
-        // Set mediaFormat parameters that may be unset.
         MediaFormatUtil.setCsdBuffers(mediaFormat, format.initializationData);
+        // Set mediaFormat parameters that may be unset.
+        if (format.language != null) {
+            mediaFormat.setString(MediaFormat.KEY_LANGUAGE, format.language);
+        }
         MediaFormatUtil.maybeSetInteger(
                 mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
         if (Util.SDK_INT >= 23) {
diff --git a/tuner/src/com/android/tv/tuner/modules/TunerModule.java b/tuner/src/com/android/tv/tuner/modules/TunerModule.java
index f149aaa..fe2482c 100644
--- a/tuner/src/com/android/tv/tuner/modules/TunerModule.java
+++ b/tuner/src/com/android/tv/tuner/modules/TunerModule.java
@@ -32,8 +32,11 @@
 import com.android.tv.tuner.exoplayer.buffer.SampleChunkIoHelper;
 import com.android.tv.tuner.exoplayer.buffer.SampleChunkIoHelperFactory;
 import com.android.tv.tuner.source.TunerSourceModule;
+import com.android.tv.tuner.tvinput.TunerRecordingSessionExoV2FactoryImpl;
 import com.android.tv.tuner.tvinput.TunerRecordingSessionFactoryImpl;
 import com.android.tv.tuner.tvinput.TunerRecordingSessionWorker;
+import com.android.tv.tuner.tvinput.TunerRecordingSessionWorkerExoV2;
+import com.android.tv.tuner.tvinput.TunerRecordingSessionWorkerExoV2Factory;
 import com.android.tv.tuner.tvinput.TunerRecordingSessionWorkerFactory;
 import com.android.tv.tuner.tvinput.TunerSessionExoV2Factory;
 import com.android.tv.tuner.tvinput.TunerSessionOverlay;
@@ -64,11 +67,24 @@
         return tunerFlags.useExoplayerV2() ? tunerSessionExoV2Factory : tunerSessionFactory;
     }
 
+    @Provides
+    static TunerRecordingSessionFactory tunerRecordingSessionFactory(
+            TunerFlags tunerFlags,
+            TunerRecordingSessionFactoryImpl tunerRecordingSessionFactoryImpl,
+            TunerRecordingSessionExoV2FactoryImpl tunerRecordingSessionExoV2FactoryImpl) {
+        return tunerFlags.useExoplayerV2() ?
+                tunerRecordingSessionExoV2FactoryImpl : tunerRecordingSessionFactoryImpl;
+    }
+
     @Binds
     abstract TunerRecordingSessionWorker.Factory tunerRecordingSessionWorkerFactory(
             TunerRecordingSessionWorkerFactory tunerRecordingSessionWorkerFactory);
 
     @Binds
+    abstract TunerRecordingSessionWorkerExoV2.Factory tunerRecordingSessionWorkerExoV2Factory(
+            TunerRecordingSessionWorkerExoV2Factory tunerRecordingSessionWorkerExoV2Factory);
+
+    @Binds
     abstract TunerSessionWorker.Factory tunerSessionWorkerFactory(
             TunerSessionWorkerFactory tunerSessionWorkerFactory);
 
@@ -89,10 +105,6 @@
             TunerSessionWorkerExoV2Factory tunerSessionWorkerExoV2Factory);
 
     @Binds
-    abstract TunerRecordingSessionFactory tunerRecordingSessionFactory(
-            TunerRecordingSessionFactoryImpl impl);
-
-    @Binds
     abstract MpegTsRendererBuilder.Factory mpegTsRendererBuilderFactory(
             MpegTsRendererBuilderFactory mpegTsRendererBuilderFactory);
 
@@ -115,4 +127,33 @@
     @Binds
     abstract SampleChunkIoHelper.Factory sampleChunkIoHelperFactory(
             SampleChunkIoHelperFactory sampleChunkIoHelperFactory);
+
+    @Binds
+    abstract com.android.tv.tuner.exoplayer2.MpegTsSampleExtractor.Factory
+    mpegTsSampleExtractorFactoryV2(
+            com.android.tv.tuner.exoplayer2.MpegTsSampleExtractorFactory
+                    mpegTsSampleExtractorFactory);
+
+    @Binds
+    abstract com.android.tv.tuner.exoplayer2.ExoPlayerSampleExtractor.Factory
+    exoPlayerSampleExtractorFactoryV2(
+            com.android.tv.tuner.exoplayer2.ExoPlayerSampleExtractorFactory
+                    exoPlayerSampleExtractorFactory);
+
+    @Binds
+    abstract com.android.tv.tuner.exoplayer2.FileSampleExtractor.Factory
+    fileSampleExtractorFactoryV2(
+            com.android.tv.tuner.exoplayer2.FileSampleExtractorFactory fileSampleExtractorFactory);
+
+    @Binds
+    abstract com.android.tv.tuner.exoplayer2.buffer.RecordingSampleBuffer.Factory
+    recordingSampleBufferFactoryV2(
+            com.android.tv.tuner.exoplayer2.buffer.RecordingSampleBufferFactory
+                    recordingSampleBufferFactory);
+
+    @Binds
+    abstract com.android.tv.tuner.exoplayer2.buffer.SampleChunkIoHelper.Factory
+    sampleChunkIoHelperFactoryV2(
+            com.android.tv.tuner.exoplayer2.buffer.SampleChunkIoHelperFactory
+                    sampleChunkIoHelperFactory);
 }
diff --git a/tuner/src/com/android/tv/tuner/setup/ScanFragment.java b/tuner/src/com/android/tv/tuner/setup/ScanFragment.java
index 8c14aaa..9da14d9 100644
--- a/tuner/src/com/android/tv/tuner/setup/ScanFragment.java
+++ b/tuner/src/com/android/tv/tuner/setup/ScanFragment.java
@@ -62,7 +62,7 @@
     private static final boolean DEBUG = false;
 
     // In the fake mode, the connection to antenna or cable is not necessary.
-    // Instead dummy channels are added.
+    // Instead fake channels are added.
     private static final boolean FAKE_MODE = false;
 
     private static final String VCTLESS_CHANNEL_NAME_FORMAT = "RF%d-%d";
diff --git a/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java b/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java
index e47162a..c1d10dc 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/BaseTunerTvInputService.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.flags.TunerFlags;
 import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
 import com.android.tv.tuner.tvinput.factory.TunerRecordingSessionFactory;
 import com.android.tv.tuner.tvinput.factory.TunerSessionFactory;
@@ -55,6 +56,7 @@
             Collections.newSetFromMap(new WeakHashMap<>());
     @Inject TunerSessionFactory mTunerSessionFactory;
     @Inject TunerRecordingSessionFactory mTunerRecordingSessionFactory;
+    @Inject TunerFlags mTunerFlags;
 
     LoadingCache<String, ChannelDataManager> mChannelDataManagers;
     RemovalListener<String, ChannelDataManager> mChannelDataManagerRemovalListener =
@@ -152,9 +154,16 @@
 
     private Uri getRecordingUri(Uri channelUri) {
         for (RecordingSession session : mTunerRecordingSession) {
-            TunerRecordingSession tunerSession = (TunerRecordingSession) session;
-            if (tunerSession.getChannelUri().equals(channelUri)) {
-                return tunerSession.getRecordingUri();
+            if (mTunerFlags.useExoplayerV2()) {
+                TunerRecordingSessionExoV2 tunerSession = (TunerRecordingSessionExoV2) session;
+                if (tunerSession.getChannelUri().equals(channelUri)) {
+                    return tunerSession.getRecordingUri();
+                }
+            } else {
+                TunerRecordingSession tunerSession = (TunerRecordingSession) session;
+                if (tunerSession.getChannelUri().equals(channelUri)) {
+                    return tunerSession.getRecordingUri();
+                }
             }
         }
         return null;
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java b/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java
index 9d689ab..47d2cc3 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionExoV2.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorkerExoV2.java b/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorkerExoV2.java
index faf006a..9481d1a 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorkerExoV2.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerRecordingSessionWorkerExoV2.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -50,17 +50,17 @@
 import com.android.tv.tuner.data.Track.AtscCaptionTrack;
 import com.android.tv.tuner.data.TunerChannel;
 import com.android.tv.tuner.dvb.DvbDeviceAccessor;
-import com.android.tv.tuner.exoplayer.ExoPlayerSampleExtractor;
-import com.android.tv.tuner.exoplayer.SampleExtractor;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.DvrStorageManager;
-import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
+import com.android.tv.tuner.exoplayer2.ExoPlayerSampleExtractor;
+import com.android.tv.tuner.exoplayer2.SampleExtractor;
+import com.android.tv.tuner.exoplayer2.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer2.buffer.DvrStorageManager;
+import com.android.tv.tuner.exoplayer2.buffer.PlaybackBufferListener;
 import com.android.tv.tuner.source.TsDataSource;
 import com.android.tv.tuner.source.TsDataSourceManager;
 import com.android.tv.tuner.ts.EventDetector.EventListener;
 import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
 
-import com.google.android.exoplayer.C;
+import com.google.android.exoplayer2.C;
 import com.google.auto.factory.AutoFactory;
 import com.google.auto.factory.Provided;
 
@@ -83,8 +83,9 @@
         implements PlaybackBufferListener,
                 EventListener,
                 SampleExtractor.OnCompletionListener,
+                SampleExtractor.Callback,
                 Handler.Callback {
-    private static final String TAG = "TunerRecordingSessionW";
+    private static final String TAG = "TunerRecordingSWExoV2";
     private static final boolean DEBUG = false;
 
     private static final String SORT_BY_TIME =
@@ -320,10 +321,7 @@
                         return true;
                     }
                     try {
-                        if (!mRecorder.prepare()) {
-                            mHandler.sendEmptyMessageDelayed(
-                                    MSG_PREPARE_RECODER, PREPARE_RECORDER_POLL_MS);
-                        }
+                        mRecorder.prepare(this);
                     } catch (IOException e) {
                         Log.w(TAG, "Failed to start recording. Couldn't prepare an extractor");
                         mSession.onError(TvInputManager.RECORDING_ERROR_UNKNOWN);
@@ -389,6 +387,11 @@
         return false;
     }
 
+    @Override
+    public void onPrepared() {
+        // Do nothing
+    }
+
     @Nullable
     private TunerChannel getChannel(Uri channelUri) {
         if (channelUri == null) {
@@ -657,7 +660,7 @@
         }
         Log.i(TAG, "recording finished " + (success ? "completely" : "partially"));
         long recordEndTime =
-                (lastExtractedPositionUs == C.UNKNOWN_TIME_US)
+                (lastExtractedPositionUs == C.TIME_UNSET)
                         ? System.currentTimeMillis()
                         : mRecordStartTime + lastExtractedPositionUs / 1000;
         updateRecordedProgramStateFinished(recordEndTime, calculateRecordingSizeInBytes());
diff --git a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2.java b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2.java
index a25c1d2..3bba28d 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2.java
@@ -20,6 +20,7 @@
 import android.content.ContentUris;
 import android.content.Context;
 import android.database.Cursor;
+import android.media.MediaFormat;
 import android.media.PlaybackParams;
 import android.media.tv.TvContentRating;
 import android.media.tv.TvContract;
@@ -60,8 +61,15 @@
 import com.android.tv.tuner.data.Track.AtscAudioTrack;
 import com.android.tv.tuner.data.Track.AtscCaptionTrack;
 import com.android.tv.tuner.data.TunerChannel;
+import com.android.tv.tuner.exoplayer2.MpegTsMediaSource;
 import com.android.tv.tuner.exoplayer2.MpegTsPlayerV2;
 import com.android.tv.tuner.exoplayer2.MpegTsPlayerV2.PlayerState;
+import com.android.tv.tuner.exoplayer2.MpegTsSampleExtractor;
+import com.android.tv.tuner.exoplayer2.SampleExtractor;
+import com.android.tv.tuner.exoplayer2.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer2.buffer.DvrStorageManager;
+import com.android.tv.tuner.exoplayer2.buffer.PlaybackBufferListener;
+import com.android.tv.tuner.exoplayer2.buffer.TrickplayStorageManager;
 import com.android.tv.tuner.prefs.TunerPreferences;
 import com.android.tv.tuner.source.TsDataSource;
 import com.android.tv.tuner.source.TsDataSourceManager;
@@ -69,8 +77,10 @@
 import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager;
 import com.android.tv.tuner.tvinput.debug.TunerDebug;
 import com.android.tv.tuner.util.StatusTextUtils;
+
 import com.google.android.exoplayer2.Format;
 import com.google.android.exoplayer2.audio.AudioCapabilities;
+import com.google.android.exoplayer2.source.MediaSource;
 import com.google.auto.factory.AutoFactory;
 import com.google.auto.factory.Provided;
 import com.google.common.collect.ImmutableList;
@@ -87,6 +97,7 @@
 // TODO: Add PlaybackBufferListener,
 @WorkerThread
 public class TunerSessionWorkerExoV2 implements
+                PlaybackBufferListener,
                 MpegTsPlayerV2.VideoEventListener,
                 MpegTsPlayerV2.Callback,
                 EventListener,
@@ -119,7 +130,6 @@
     private static final int MSG_SCHEDULE_OF_PROGRAMS = 1009;
     private static final int MSG_UPDATE_CHANNEL_INFO = 1010;
     private static final int MSG_TRICKPLAY_BY_SEEK = 1011;
-    private static final int MSG_SMOOTH_TRICKPLAY_MONITOR = 1012;
     private static final int MSG_PARENTAL_CONTROLS = 1015;
     private static final int MSG_RESCHEDULE_PROGRAMS = 1016;
     private static final int MSG_BUFFER_START_TIME_CHANGED = 1017;
@@ -164,7 +174,6 @@
     // Actual interval would be divided by the speed.
     private static final int EXPECTED_KEY_FRAME_INTERVAL_MS = 500;
     private static final int MIN_TRICKPLAY_SEEK_INTERVAL_MS = 20;
-    private static final int TRICKPLAY_MONITOR_INTERVAL_MS = 250;
     private static final int RELEASE_WAIT_INTERVAL_MS = 50;
     private static final long TRICKPLAY_OFF_DURATION_MS = TimeUnit.DAYS.toMillis(14);
     private static final long SEEK_MARGIN_MS = TimeUnit.SECONDS.toMillis(2);
@@ -183,13 +192,13 @@
     private final int mMaxTrickplayBufferSizeMb;
     private final File mTrickplayBufferDir;
     private final @TRICKPLAY_MODE int mTrickplayModeCustomization;
-
+    private final MpegTsSampleExtractor.Factory mMpegTsSampleExtractorFactory;
     private volatile Surface mSurface;
     private volatile float mVolume = 1.0f;
     private volatile boolean mCaptionEnabled;
     private volatile MpegTsPlayerV2 mPlayer;
     private volatile TunerChannel mChannel;
-    private volatile Long mRecordingDuration;
+    private volatile Long mRecordingDuration = 0L;
     private volatile long mRecordStartTimeMs;
     private volatile long mBufferStartTimeMs;
     private volatile boolean mTrickplayDisabledByStorageIssue;
@@ -232,6 +241,7 @@
 
     private int mSignalStrength;
     private long mRecordedProgramStartTimeMs;
+    private BufferManager mBufferManager;
 
     /**
      * Factory for {@link TunerSessionWorkerExoV2}.
@@ -254,6 +264,7 @@
             TunerSessionExoV2 tunerSession,
             TunerSessionOverlay tunerSessionOverlay,
             @Provided LegacyFlags legacyFlags,
+            @Provided MpegTsSampleExtractor.Factory mpegTsSampleExtractorFactory,
             @Provided TsDataSourceManager.Factory tsDataSourceManagerFactory) {
         this(
                 context,
@@ -262,6 +273,7 @@
                 tunerSessionOverlay,
                 null,
                 legacyFlags,
+                mpegTsSampleExtractorFactory,
                 tsDataSourceManagerFactory);
     }
 
@@ -273,6 +285,7 @@
             TunerSessionOverlay tunerSessionOverlay,
             @Nullable Handler handler,
             LegacyFlags legacyFlags,
+            MpegTsSampleExtractor.Factory mpegTsSampleExtractorFactory,
             TsDataSourceManager.Factory tsDataSourceManagerFactory) {
         mLegacyFlags = legacyFlags;
         if (DEBUG) {
@@ -291,6 +304,7 @@
         mSession = tunerSession;
         mTunerSessionOverlay = tunerSessionOverlay;
         mChannelDataManager = channelDataManager;
+        mMpegTsSampleExtractorFactory = mpegTsSampleExtractorFactory;
         mRecordingUri = null;
         mChannelDataManager.setListener(this);
         mChannelDataManager.checkDataVersion(mContext);
@@ -401,9 +415,21 @@
         return Uri.parse(mRecordingId).getPath();
     }
 
-    private Long getDurationForRecording(String recordingId) {
-        // TODO: Get recording duration
-        return null;
+    private Long getDurationForRecording() {
+        DvrStorageManager storageManager =
+                new DvrStorageManager(new File(getRecordingPath()), false);
+        List<BufferManager.TrackFormat> trackFormatList = storageManager.readTrackInfoFiles(false);
+        if (trackFormatList.isEmpty()) {
+            trackFormatList = storageManager.readTrackInfoFiles(true);
+        }
+        if (!trackFormatList.isEmpty()) {
+            BufferManager.TrackFormat trackFormat = trackFormatList.get(0);
+            Long durationUs = trackFormat.mediaFormat.getLong(MediaFormat.KEY_DURATION);
+            // we need duration by milli for trickplay notification.
+            return durationUs != null ? durationUs / 1000 : 0L;
+        }
+        Log.e(TAG, "meta file for recording was not found: " + mRecordingId);
+        return 0L;
     }
 
     @MainThread
@@ -454,6 +480,9 @@
         synchronized (mReleaseLock) {
             mReleaseRequested = true;
         }
+        if (mIsActiveSession) {
+            sActiveSessionSemaphore.release();
+        }
         if (mHasSoftwareAudioDecoder) {
             // TODO reimplement for google3
             // Here disconnect ffmpeg
@@ -474,9 +503,7 @@
         mReadyStartTimeMs = INVALID_TIME;
         mBufferingStartTimeMs = INVALID_TIME;
         if (playbackState == MpegTsPlayerV2.STATE_READY) {
-            if (DEBUG) {
-                Log.d(TAG, "ExoPlayerV2 ready");
-            }
+            if (DEBUG) Log.d(TAG, "ExoPlayerV2 ready");
             if (!mPlayerStarted) {
                 sendMessage(MSG_START_PLAYBACK, System.identityHashCode(mPlayer));
             }
@@ -529,10 +556,8 @@
 
     @Override
     public void onRenderedFirstFrame() {
-        if (mSurface != null && mPlayerStarted) {
-            if (DEBUG) {
-                Log.d(TAG, "MSG_DRAWN_TO_SURFACE");
-            }
+        if (!mReportedDrawnToSurface && mSurface != null && mPlayerStarted) {
+            if (DEBUG) Log.d(TAG, "MSG_DRAWN_TO_SURFACE");
             if (mRecordingId != null) {
                 // Workaround of b/33298048: set it to 1 instead of 0.
                 mBufferStartTimeMs = mRecordStartTimeMs = 1;
@@ -562,10 +587,9 @@
 
     @Override
     public void onSmoothTrickplayForceStopped() {
-        if (mPlayer == null || !mHandler.hasMessages(MSG_SMOOTH_TRICKPLAY_MONITOR)) {
+        if (mPlayer == null) {
             return;
         }
-        mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
         doTrickplayBySeek((int) mPlayer.getCurrentPosition());
     }
 
@@ -585,11 +609,6 @@
     }
 
     @Override
-    public void onClearCaptionEvent() {
-        mTunerSessionOverlay.sendUiMessage(TunerSessionOverlay.MSG_UI_CLEAR_CAPTION_RENDERER);
-    }
-
-    @Override
     public void onDiscoverCaptionServiceNumber(int serviceNumber) {
         sendMessage(MSG_DISCOVER_CAPTION_SERVICE_NUMBER, serviceNumber);
     }
@@ -615,6 +634,22 @@
         sendMessage(MSG_PROGRAM_DATA_RESULT, Pair.create(channel, programs));
     }
 
+    // PlaybackBufferListener
+    @Override
+    public void onBufferStartTimeChanged(long startTimeMs) {
+        sendMessage(MSG_BUFFER_START_TIME_CHANGED, startTimeMs);
+    }
+
+    @Override
+    public void onBufferStateChanged(boolean available) {
+        sendMessage(MSG_BUFFER_STATE_CHANGED, available);
+    }
+
+    @Override
+    public void onDiskTooSlow() {
+        mTrickplayDisabledByStorageIssue = true;
+        sendMessage(MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer));
+    }
 
     // EventDetector.EventListener
     @Override
@@ -745,8 +780,6 @@
                 return handleMessageProgramDataResult(msg);
             case MSG_TRICKPLAY_BY_SEEK:
                 return handleMessageTrickplayBySeek(msg.arg1);
-            case MSG_SMOOTH_TRICKPLAY_MONITOR:
-                return handleMessageSmoothTrickplayMonitor();
             case MSG_RESCHEDULE_PROGRAMS:
                 return handleMessageReschedulePrograms();
             case MSG_PARENTAL_CONTROLS:
@@ -793,9 +826,7 @@
             Log.d(TAG, "MSG_TUNE");
         }
 
-        // When sequential tuning messages arrived, it skips middle tuning messages in
-        // order
-        // to change to the last requested channel quickly.
+        // There's a pending tune which will override this one, so we ignore the current message.
         if (mHandler.hasMessages(MSG_TUNE)) {
             return true;
         }
@@ -849,9 +880,8 @@
             mChannelDataManager.requestProgramsData(channel);
         }
         prepareTune(channel, recording);
-        // TODO: Need to refactor. notifyContentAllowed() should not be called if
-        // parental
-        // control is turned on.
+        // TODO: Need to refactor. notifyContentAllowed() should not be called if parental control
+        //  is turned on.
         mSession.notifyContentAllowed();
         resetTvTracks();
         resetPlayback();
@@ -881,9 +911,6 @@
         stopCaptionTrack();
         mSourceManager.release();
         mHandler.getLooper().quitSafely();
-        if (mIsActiveSession) {
-            sActiveSessionSemaphore.release();
-        }
         return true;
     }
 
@@ -902,8 +929,7 @@
                 resetPlayback();
             } else {
                 // When it reaches this point, it may be due to an error that occurred
-                // in
-                // the tuner device. Calling stopPlayback() resets the tuner device
+                // in the tuner device. Calling stopPlayback() resets the tuner device
                 // to recover from the error.
                 stopPlayback(false);
                 stopCaptionTrack();
@@ -912,8 +938,7 @@
                 Log.i(TAG, "Notify weak signal since fail to retry playback");
 
                 // After MAX_IMMEDIATE_RETRY_COUNT, give some delay of an empirically
-                // chosen
-                // value before recovering the playback.
+                // chosen value before recovering the playback.
                 mHandler.sendEmptyMessageDelayed(
                         MSG_RESET_PLAYBACK, RECOVER_STOPPED_PLAYBACK_PERIOD_MS);
             }
@@ -1029,39 +1054,6 @@
         return true;
     }
 
-    private boolean handleMessageSmoothTrickplayMonitor() {
-        if (mPlayer == null) {
-            return true;
-        }
-        long systemCurrentTime = System.currentTimeMillis();
-        long position = getCurrentPosition();
-        if (mRecordingId == null) {
-            // Checks if the position exceeds the upper bound when forwarding,
-            // or exceed the lower bound when rewinding.
-            // If the direction is not checked, there can be some issues.
-            // (See b/29939781 for more details.)
-            if ((position > systemCurrentTime && mPlaybackParams.getSpeed() > 0L)
-                    || (position < mBufferStartTimeMs && mPlaybackParams.getSpeed() < 0L)) {
-                doTimeShiftResume();
-                return true;
-            }
-        } else {
-            if (position > mRecordingDuration || position < 0) {
-                doTimeShiftPause();
-                return true;
-            }
-            long systemBufferTime =
-                    systemCurrentTime - SEEK_MARGIN_MS - mRecordedProgramStartTimeMs;
-            if (position > systemBufferTime) {
-                doTimeShiftResume();
-                return true;
-            }
-        }
-        mHandler.sendEmptyMessageDelayed(
-                MSG_SMOOTH_TRICKPLAY_MONITOR, TRICKPLAY_MONITOR_INTERVAL_MS);
-        return true;
-    }
-
     private boolean handleMessageReschedulePrograms() {
         if (mHandler.hasMessages(MSG_SCHEDULE_OF_PROGRAMS)) {
             mHandler.sendEmptyMessage(MSG_RESCHEDULE_PROGRAMS);
@@ -1411,10 +1403,23 @@
                 }
             }
         }
-
-        // TODO: Add support for BufferManager
-
-        MpegTsPlayerV2 player = new MpegTsPlayerV2(mContext, mSourceManager, this);
+        mBufferManager = null;
+        if (mRecordingId != null) {
+            BufferManager.StorageManager storageManager =
+                    new DvrStorageManager(new File(getRecordingPath()), false);
+            mBufferManager = new BufferManager(storageManager);
+            updateCaptionTracks(((DvrStorageManager) storageManager).readCaptionInfoFiles());
+        } else if (!mTrickplayDisabledByStorageIssue
+                && mTrickplaySetting != TunerPreferences.TRICKPLAY_SETTING_DISABLED
+                && mMaxTrickplayBufferSizeMb >= MIN_BUFFER_SIZE_DEF) {
+            mBufferManager =
+                    new BufferManager(
+                            new TrickplayStorageManager(
+                                    mContext,
+                                    mTrickplayBufferDir,
+                                    1024L * 1024 * mMaxTrickplayBufferSizeMb));
+        }
+        MpegTsPlayerV2 player = new MpegTsPlayerV2(mContext,this);
         player.setVideoEventListener(this);
         player.setCaptionServiceNumber(
                 mCaptionTrack != null
@@ -1692,30 +1697,41 @@
     @VisibleForTesting
     protected void preparePlayback() {
         MpegTsPlayerV2 player = createPlayer(mAudioCapabilities);
-        if (!player.prepare(mChannel, this)) {
-            mSourceManager.setKeepTuneStatus(false);
-            player.release();
-            if (!mHandler.hasMessages(MSG_TUNE)) {
-                // When prepare failed, there may be some errors related to hardware. In that
-                // case, retry playback immediately may not help.
-                notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
-                Log.i(TAG, "Notify weak signal due to player preparation failure");
-                mHandler.sendMessageDelayed(
-                        mHandler.obtainMessage(
-                                MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)),
-                        PLAYBACK_RETRY_DELAY_MS);
+        TsDataSource source = null;
+        MediaSource mediaSource;
+        if (mChannel != null) {
+            source = mSourceManager.createDataSource(mContext, mChannel, this);
+            if (source == null) {
+                mSourceManager.setKeepTuneStatus(false);
+                player.release();
+                if (!mHandler.hasMessages(MSG_TUNE)) {
+                    // When prepare failed, there may be some errors related to hardware. In that
+                    // case, retry playback immediately may not help.
+                    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL);
+                    Log.i(TAG, "Notify weak signal due to player preparation failure");
+                    mHandler.sendMessageDelayed(
+                            mHandler.obtainMessage(
+                                    MSG_RETRY_PLAYBACK, System.identityHashCode(mPlayer)),
+                            PLAYBACK_RETRY_DELAY_MS);
+                }
+                return;
             }
-        } else {
-            mPlayer = player;
-            mPlayerStarted = false;
-            mHandler.removeMessages(MSG_CHECK_SIGNAL);
-            mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS);
-            if (mHandler.hasMessages(MSG_CHECK_SIGNAL_STRENGTH)) {
-                mHandler.removeMessages(MSG_CHECK_SIGNAL_STRENGTH);
-            }
-            if (CommonFeatures.TUNER_SIGNAL_STRENGTH.isEnabled(mContext)) {
-                mHandler.sendEmptyMessage(MSG_CHECK_SIGNAL_STRENGTH);
-            }
+        }
+        SampleExtractor extractor =
+                source == null ?
+                        mMpegTsSampleExtractorFactory.create(
+                                mBufferManager, this, mRecordingDuration) :
+                        mMpegTsSampleExtractorFactory.create(source, mBufferManager, this);
+        mediaSource = new MpegTsMediaSource(extractor);
+        player.prepare(source, mediaSource);
+        mPlayer = player;
+        mHandler.removeMessages(MSG_CHECK_SIGNAL);
+        mHandler.sendEmptyMessageDelayed(MSG_CHECK_SIGNAL, CHECK_NO_SIGNAL_INITIAL_DELAY_MS);
+        if (mHandler.hasMessages(MSG_CHECK_SIGNAL_STRENGTH)) {
+            mHandler.removeMessages(MSG_CHECK_SIGNAL_STRENGTH);
+        }
+        if (CommonFeatures.TUNER_SIGNAL_STRENGTH.isEnabled(mContext)) {
+            mHandler.sendEmptyMessage(MSG_CHECK_SIGNAL_STRENGTH);
         }
     }
 
@@ -1743,7 +1759,8 @@
         mRetryCount = 0;
         mChannel = channel;
         mRecordingId = recording;
-        mRecordingDuration = recording != null ? getDurationForRecording(recording) : null;
+        // TODO: Use asynchronous task to update this value.
+        mRecordingDuration = recording != null ? getDurationForRecording() : 0L;
         mProgram = null;
         mPrograms = null;
         if (mRecordingId != null) {
@@ -1845,7 +1862,6 @@
     }
 
     private void doTimeShiftPause() {
-        mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
         mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
         if (!hasEnoughBackwardBuffer()) {
             return;
@@ -1856,7 +1872,6 @@
     }
 
     private void doTimeShiftResume() {
-        mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
         mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
         mPlaybackParams.setSpeed(1.0f);
         mPlayer.setPlayWhenReady(true);
@@ -1864,7 +1879,6 @@
     }
 
     private void doTimeShiftSeekTo(long timeMs) {
-        mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
         mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
         mPlayer.seekTo((int) (timeMs - mRecordStartTimeMs));
     }
@@ -1876,17 +1890,9 @@
         mPlaybackParams = params;
         float speed = mPlaybackParams.getSpeed();
         if (speed == 1.0f) {
-            mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
             mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
             doTimeShiftResume();
-        } else if (mPlayer.supportSmoothTrickPlay(speed)) {
-            mHandler.removeMessages(MSG_TRICKPLAY_BY_SEEK);
-            mPlayer.setAudioTrackAndClosedCaption(false);
-            mPlayer.startSmoothTrickplay(mPlaybackParams);
-            mHandler.sendEmptyMessageDelayed(
-                    MSG_SMOOTH_TRICKPLAY_MONITOR, TRICKPLAY_MONITOR_INTERVAL_MS);
         } else {
-            mHandler.removeMessages(MSG_SMOOTH_TRICKPLAY_MONITOR);
             if (!mHandler.hasMessages(MSG_TRICKPLAY_BY_SEEK)) {
                 mPlayer.setAudioTrackAndClosedCaption(false);
                 mPlayer.setPlayWhenReady(false);
diff --git a/tuner/src/com/android/tv/tuner/tvinput/datamanager/ChannelDataManager.java b/tuner/src/com/android/tv/tuner/tvinput/datamanager/ChannelDataManager.java
index 447618a..b06d15c 100644
--- a/tuner/src/com/android/tv/tuner/tvinput/datamanager/ChannelDataManager.java
+++ b/tuner/src/com/android/tv/tuner/tvinput/datamanager/ChannelDataManager.java
@@ -283,7 +283,7 @@
      * obsolete channels, which are previously scanned but are not in the current scanned result.
      */
     public void notifyScanCompleted() {
-        // Send a dummy message to check whether there is any MSG_HANDLE_CHANNEL in queue
+        // Send an empty message to check whether there is any MSG_HANDLE_CHANNEL in queue
         // and avoid race conditions.
         scanCompleted.set(true);
         mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_HANDLE_CHANNEL, null));
@@ -635,7 +635,7 @@
     private void clearChannels() {
         int count = mContext.getContentResolver().delete(mChannelsUri, null, null);
         if (count > 0) {
-            // We have just deleted obsolete data. Now tell the user that he or she needs
+            // We have just deleted obsolete data. Now tell the user that they need
             // to perform the auto-scan again.
             if (mListener != null) {
                 mListener.onRescanNeeded();
diff --git a/tuner/tests/robotests/Android.mk b/tuner/tests/robotests/Android.mk
index 16af9e9..bf2f24c 100644
--- a/tuner/tests/robotests/Android.mk
+++ b/tuner/tests/robotests/Android.mk
@@ -5,6 +5,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := TvTunerRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 
 LOCAL_SRC_FILES := $(call all-java-files-under, javatests)
@@ -42,6 +44,8 @@
 #############################################################
 include $(CLEAR_VARS)
 LOCAL_MODULE := RunTvTunerRoboTests
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 
 BASE_DIR = com/android/tv/tuner
 EXCLUDE_FILES := \
diff --git a/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2Test.java b/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2Test.java
index 6ef0918..e34ef00 100644
--- a/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2Test.java
+++ b/tuner/tests/robotests/javatests/com/android/tv/tuner/tvinput/TunerSessionWorkerExoV2Test.java
@@ -25,6 +25,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.support.annotation.Nullable;
 import android.view.accessibility.CaptioningManager;
 
 import com.android.tv.common.CommonConstants;
@@ -37,6 +38,9 @@
 import com.android.tv.testing.constants.ConfigConstants;
 import com.android.tv.tuner.cc.CaptionTrackRenderer;
 import com.android.tv.tuner.exoplayer2.MpegTsPlayerV2;
+import com.android.tv.tuner.exoplayer2.MpegTsSampleExtractor;
+import com.android.tv.tuner.exoplayer2.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer2.buffer.PlaybackBufferListener;
 import com.android.tv.tuner.source.TsDataSourceManager;
 import com.android.tv.tuner.source.TunerTsStreamerManager;
 import com.android.tv.tuner.testing.TvTunerRobolectricTestRunner;
@@ -44,6 +48,7 @@
 import com.android.tv.tuner.tvinput.TunerSessionOverlay;
 
 import com.google.android.exoplayer2.audio.AudioCapabilities;
+import com.google.android.exoplayer2.upstream.DataSource;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -108,6 +113,28 @@
                                                 captionLayout,
                                                 context2 -> null,
                                                 mTunerFlags));
+        MpegTsSampleExtractor.Factory mpegTsSampleExtractorFactory =
+                new MpegTsSampleExtractor.Factory() {
+                    @Override
+                    public MpegTsSampleExtractor create(
+                            BufferManager bufferManager,
+                            PlaybackBufferListener bufferListener,
+                            long durationMs) {
+                        return new MpegTsSampleExtractor(
+                                bufferManager,
+                                bufferListener,
+                                durationMs,
+                                (bufferManager1, bufferListener1, durationMs1) -> null);
+                    }
+
+                    @Override
+                    public MpegTsSampleExtractor create(
+                            DataSource source,
+                            @Nullable BufferManager bufferManager,
+                            PlaybackBufferListener bufferListener) {
+                        return null;
+                    }
+                };
 
         new TunerSessionExoV2(
                 context,
@@ -123,6 +150,7 @@
                                     tunerSessionOverlay,
                                     mHandler,
                                     mLegacyFlags,
+                                    mpegTsSampleExtractorFactory,
                                     tsDataSourceManagerFactory) {
                                 @Override
                                 protected void notifySignal(int signal) {
@@ -182,30 +210,10 @@
     }
 
     @Test
-    public void preparePlayback_playerIsNotReady() {
-        Mockito.when(
-                        mPlayer.prepare(
-                                ArgumentMatchers.any(),
-                                ArgumentMatchers.any()))
-                .thenReturn(false);
-        tunerSessionWorker.preparePlayback();
-        assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_TUNE)).isFalse();
-        assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_RETRY_PLAYBACK)).isTrue();
-        assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH)).isFalse();
-        assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL)).isFalse();
-    }
-
-    @Test
-    @Ignore
     public void preparePlayback_playerIsReady() {
-        Mockito.when(
-                        mPlayer.prepare(
-                                ArgumentMatchers.any(),
-                                ArgumentMatchers.any()))
-                .thenReturn(true);
         tunerSessionWorker.preparePlayback();
         assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_RETRY_PLAYBACK)).isFalse();
-        assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH)).isTrue();
+        assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL_STRENGTH)).isFalse();
         assertThat(mHandler.hasMessages(TunerSessionWorker.MSG_CHECK_SIGNAL)).isTrue();
     }
 }
diff --git a/tuner/tests/testing/Android.mk b/tuner/tests/testing/Android.mk
index 864f5f3..38f7342 100644
--- a/tuner/tests/testing/Android.mk
+++ b/tuner/tests/testing/Android.mk
@@ -19,6 +19,8 @@
 
 LOCAL_INSTRUMENTATION_FOR := LiveTv
 LOCAL_MODULE := tv-tuner-testing
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_TAGS := optional
 LOCAL_SDK_VERSION := system_current
 
diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTestExoV2.java b/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTestExoV2.java
index b2478eb..359e7bb 100644
--- a/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTestExoV2.java
+++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/ZappingTimeTestExoV2.java
@@ -21,32 +21,39 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
-import android.support.annotation.Nullable;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
+import android.util.Pair;
 import android.view.Surface;
 
 import androidx.test.filters.LargeTest;
 
+import com.android.tv.tuner.api.Tuner;
+import com.android.tv.tuner.api.TunerFactory;
 import com.android.tv.tuner.data.Cea708Data;
 import com.android.tv.tuner.data.Channel.AudioStreamType;
 import com.android.tv.tuner.data.Channel.VideoStreamType;
 import com.android.tv.tuner.data.PsiData;
 import com.android.tv.tuner.data.PsipData;
 import com.android.tv.tuner.data.TunerChannel;
-import com.android.tv.tuner.exoplayer.MpegTsPlayer;
-import com.android.tv.tuner.exoplayer.MpegTsRendererBuilder;
-import com.android.tv.tuner.exoplayer.MpegTsSampleExtractor;
-import com.android.tv.tuner.exoplayer.buffer.BufferManager;
-import com.android.tv.tuner.exoplayer.buffer.PlaybackBufferListener;
-import com.android.tv.tuner.exoplayer.buffer.TrickplayStorageManager;
+import com.android.tv.tuner.exoplayer2.ExoPlayerSampleExtractor;
+import com.android.tv.tuner.exoplayer2.MpegTsMediaSource;
+import com.android.tv.tuner.exoplayer2.MpegTsPlayerV2;
+import com.android.tv.tuner.exoplayer2.MpegTsSampleExtractor;
+import com.android.tv.tuner.exoplayer2.SampleExtractor;
+import com.android.tv.tuner.exoplayer2.buffer.BufferManager;
+import com.android.tv.tuner.exoplayer2.buffer.PlaybackBufferListener;
+import com.android.tv.tuner.exoplayer2.buffer.RecordingSampleBuffer;
+import com.android.tv.tuner.exoplayer2.buffer.SampleChunkIoHelper;
+import com.android.tv.tuner.exoplayer2.buffer.TrickplayStorageManager;
+import com.android.tv.tuner.source.TsDataSource;
 import com.android.tv.tuner.source.TsDataSourceManager;
 import com.android.tv.tuner.source.TsDataSourceManager.Factory;
 import com.android.tv.tuner.source.TunerTsStreamerManager;
 import com.android.tv.tuner.ts.EventDetector.EventListener;
 
-import com.google.android.exoplayer.ExoPlayer;
-import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.ExoPlayer;
+import com.google.android.exoplayer2.source.MediaSource;
 
 import javax.inject.Provider;
 import org.junit.Ignore;
@@ -86,7 +93,7 @@
 
     private TunerChannel mChannel;
     private FileTunerHal mTunerHal;
-    private MpegTsPlayer mPlayer;
+    private MpegTsPlayerV2 mPlayer;
     private TsDataSourceManager mSourceManager;
     private Handler mHandler;
     private Context mTargetContext;
@@ -99,6 +106,7 @@
     private MockMpegTsPlayerListener mMpegTsPlayerListener = new MockMpegTsPlayerListener();
     private MockPlaybackBufferListener mPlaybackBufferListener = new MockPlaybackBufferListener();
     private MockChannelScanListener mEventListener = new MockChannelScanListener();
+    private ExoPlayerSampleExtractor.Factory mExoPlayerSampleExtractorFactory;
 
     @Override
     protected void setUp() throws Exception {
@@ -126,31 +134,27 @@
         mChannel.setModulation(MODULATION);
         mTunerHal = new FileTunerHal(context, tsCacheFile);
         mTunerHal.openFirstAvailable();
+        TunerFactory tunerFactory = new TunerFactory() {
+            @Override
+            public Tuner createInstance(Context context) {
+                return null;
+            }
+
+            @Override
+            public boolean useBuiltInTuner(Context context) {
+                return false;
+            }
+
+            @Override
+            public Pair<Integer, Integer> getTunerTypeAndCount(Context context) {
+                return null;
+            }
+        };
         Provider<TunerTsStreamerManager> tsStreamerManagerProvider =
-                () -> new TunerTsStreamerManager(null);
+                () -> new TunerTsStreamerManager(tunerFactory);
         TsDataSourceManager.Factory tsFactory = new Factory(tsStreamerManagerProvider);
         mSourceManager = tsFactory.create(false);
         mSourceManager.addTunerHalForTest(mTunerHal);
-        MpegTsSampleExtractor.Factory mpegTsSampleExtractorFactory =
-                new MpegTsSampleExtractor.Factory() {
-                    @Override
-                    public MpegTsSampleExtractor create(
-                            BufferManager bufferManager, PlaybackBufferListener bufferListener) {
-                        return null;
-                    }
-
-                    @Override
-                    public MpegTsSampleExtractor create(
-                            DataSource source,
-                            @Nullable BufferManager bufferManager,
-                            PlaybackBufferListener bufferListener) {
-                        return new MpegTsSampleExtractor(
-                                source,
-                                bufferManager,
-                                bufferListener,
-                                (uri, source1, manager, listener, isRecording) -> null);
-                    }
-                };
         mHandler =
                 new Handler(
                         handlerThread.getLooper(),
@@ -160,48 +164,7 @@
                                 switch (msg.what) {
                                     case MSG_START_PLAYBACK:
                                         {
-                                            mHandler.removeCallbacksAndMessages(null);
-                                            stopPlayback();
-                                            mOnDrawnToSurfaceTimeMs.set(0);
-                                            mDrawnToSurfaceLatch = new CountDownLatch(1);
-                                            if (mWaitTuneExecuteLatch != null) {
-                                                mWaitTuneExecuteLatch.countDown();
-                                            }
-                                            int frequency = msg.arg1;
-                                            boolean useSimpleSampleBuffer = (msg.arg2 == 1);
-                                            BufferManager bufferManager = null;
-                                            if (!useSimpleSampleBuffer) {
-                                                bufferManager =
-                                                        new BufferManager(
-                                                                new TrickplayStorageManager(
-                                                                        mTargetContext,
-                                                                        mTrickplayBufferDir,
-                                                                        1024L
-                                                                                * 1024L
-                                                                                * BUFFER_SIZE_DEF));
-                                            }
-                                            mChannel.setFrequency(frequency);
-                                            mSourceManager.setKeepTuneStatus(true);
-
-                                            mPlayer =
-                                                    new MpegTsPlayer(
-                                                            new MpegTsRendererBuilder(
-                                                                    mTargetContext,
-                                                                    bufferManager,
-                                                                    mPlaybackBufferListener,
-                                                                    mpegTsSampleExtractorFactory),
-                                                            mHandler,
-                                                            mSourceManager,
-                                                            null,
-                                                            mMpegTsPlayerListener);
-                                            mPlayer.setCaptionServiceNumber(
-                                                    Cea708Data.EMPTY_SERVICE_NUMBER);
-                                            mPlayer.prepare(
-                                                    mTargetContext,
-                                                    mChannel,
-                                                    false,
-                                                    mEventListener);
-                                            return true;
+                                            handleMessageStartPlayback(msg.arg1, msg.arg2 == 1);
                                         }
                                     default:
                                         {
@@ -211,6 +174,70 @@
                                 }
                             }
                         });
+        SampleChunkIoHelper.Factory sampleChunkIoHelperFactory =
+                (ids, mediaFormats, bufferReason, bufferManager, samplePool, ioCallback) ->
+                        new SampleChunkIoHelper(
+                                ids,
+                                mediaFormats,
+                                bufferReason,
+                                bufferManager,
+                                samplePool,
+                                ioCallback);
+        RecordingSampleBuffer.Factory recordingSampleBufferFactory =
+                (bufferManager, bufferListener, enableTrickplay, bufferReason) ->
+                        new RecordingSampleBuffer(
+                            bufferManager,
+                            bufferListener,
+                            enableTrickplay,
+                            bufferReason,
+                            sampleChunkIoHelperFactory);
+        mExoPlayerSampleExtractorFactory =
+                (uri, source, bufferManager, bufferListener, isRecording) ->
+                        new ExoPlayerSampleExtractor(
+                            uri,
+                            source,
+                            bufferManager,
+                            bufferListener,
+                            isRecording,
+                            recordingSampleBufferFactory);
+    }
+
+    private boolean handleMessageStartPlayback(int frequency, boolean useSimpleSampleBuffer) {
+        mHandler.removeCallbacksAndMessages(null);
+        stopPlayback();
+        mOnDrawnToSurfaceTimeMs.set(0);
+        mDrawnToSurfaceLatch = new CountDownLatch(1);
+        if (mWaitTuneExecuteLatch != null) {
+            mWaitTuneExecuteLatch.countDown();
+        }
+        BufferManager bufferManager = null;
+        if (!useSimpleSampleBuffer) {
+            bufferManager =
+                    new BufferManager(
+                            new TrickplayStorageManager(
+                                    mTargetContext,
+                                    mTrickplayBufferDir,
+                                    1024L * 1024L * BUFFER_SIZE_DEF));
+        }
+        mChannel.setFrequency(frequency);
+        mSourceManager.setKeepTuneStatus(true);
+        mPlayer = new MpegTsPlayerV2(mTargetContext, mMpegTsPlayerListener);
+        mPlayer.setCaptionServiceNumber(
+                Cea708Data.EMPTY_SERVICE_NUMBER);
+        TsDataSource source =
+                mSourceManager.createDataSource(mTargetContext, mChannel, mEventListener);
+        if (source == null) {
+            return false;
+        }
+        SampleExtractor extractor =
+                        new MpegTsSampleExtractor(
+                                source,
+                                bufferManager,
+                                mPlaybackBufferListener,
+                                mExoPlayerSampleExtractorFactory);
+        MediaSource mediaSource = new MpegTsMediaSource(extractor);
+        mPlayer.prepare(source, mediaSource);
+        return true;
     }
 
     @Override
@@ -345,12 +372,12 @@
         }
     }
 
-    private class MockMpegTsPlayerListener implements MpegTsPlayer.Listener {
+    private class MockMpegTsPlayerListener implements MpegTsPlayerV2.Callback {
 
         @Override
-        public void onStateChanged(boolean playWhenReady, int playbackState) {
+        public void onStateChanged(int playbackState) {
             if (DEBUG) {
-                Log.d(TAG, "ExoPlayer state change: " + playbackState + " " + playWhenReady);
+                Log.d(TAG, "ExoPlayer state change: " + playbackState);
             }
             if (playbackState == ExoPlayer.STATE_READY) {
                 mPlayer.setSurface(mSurface);
@@ -377,7 +404,7 @@
         }
 
         @Override
-        public void onDrawnToSurface(MpegTsPlayer player, Surface surface) {
+        public void onRenderedFirstFrame() {
             if (DEBUG) {
                 Log.d(TAG, "onDrawnToSurface");
             }