Merge Android Pie into master

Bug: 112104996
Change-Id: Iec32f622f0bb1bc1583aefcbf6d115ad05baa693
diff --git a/Android.mk b/Android.mk
index 76e429b..351d8b5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,14 +23,10 @@
 
 include $(LOCAL_PATH)/version.mk
 
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-proto-files-under, proto)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := LiveTv
 
-
-
 # It is required for com.android.providers.tv.permission.ALL_EPG_DATA
 LOCAL_PRIVILEGED_MODULE := true
 
@@ -41,26 +37,29 @@
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/res \
-    $(LOCAL_PATH)/usbtuner-res
-
-LOCAL_SRC_FILES += \
-    src/com/android/tv/tuner/exoplayer/ffmpeg/IFfmpegDecoder.aidl
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    icu4j-usbtuner \
+    android-support-annotations \
     lib-exoplayer \
-    lib-exoplayer-v2 \
-    lib-exoplayer-v2-ext-ffmpeg
+    lib-exoplayer-v2-core \
+    jsr330 \
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
-    android-support-annotations \
     android-support-compat \
     android-support-core-ui \
     android-support-tv-provider \
+    android-support-v4 \
+    android-support-v7-appcompat \
     android-support-v7-palette \
+    android-support-v7-preference \
     android-support-v7-recyclerview \
+    android-support-v14-preference \
     android-support-v17-leanback \
-    tv-common
+    android-support-v17-preference-leanback \
+    live-channels-partner-support \
+    live-tv-tuner \
+    tv-common \
+
 
 LOCAL_JAVACFLAGS := -Xlint:deprecation -Xlint:unchecked
 
@@ -68,48 +67,36 @@
     --version-name "$(version_name_package)" \
     --version-code $(version_code_package) \
 
-LOCAL_PROGUARD_FLAG_FILES := proguard.flags
-
-
 LOCAL_JNI_SHARED_LIBRARIES := libtunertvinput_jni
 LOCAL_AAPT_FLAGS += --extra-packages com.android.tv.tuner
 
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/proto/
-
 include $(BUILD_PACKAGE)
 
-# --------------------------------------------------------------
-# Build a tiny icu4j library out of the classes necessary for the project.
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := icu4j-usbtuner
-LOCAL_MODULE_TAGS := optional
-icu4j_path := icu/icu4j
-LOCAL_SRC_FILES := \
-    $(icu4j_path)/main/classes/core/src/com/ibm/icu/text/SCSU.java \
-    $(icu4j_path)/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java
-LOCAL_SDK_VERSION := system_current
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-
 #############################################################
 # Pre-built dependency jars
 #############################################################
-include $(CLEAR_VARS)
+prebuilts := \
+    lib-exoplayer:libs/exoplayer-r1.5.16.aar \
+    lib-exoplayer-v2-core:libs/exoplayer-core-2-SNAPHOT-20180114.aar \
+    auto-value-jar:../../../prebuilts/tools/common/m2/repository/com/google/auto/value/auto-value/1.5.2/auto-value-1.5.2.jar \
+    javax-annotations-jar:../../../prebuilts/tools/common/m2/repository/javax/annotation/javax.annotation-api/1.2/javax.annotation-api-1.2.jar \
+    truth-0-36-prebuilt-jar:../../../prebuilts/tools/common/m2/repository/com/google/truth/truth/0.36/truth-0.36.jar \
 
-LOCAL_MODULE_TAGS := optional
+define define-prebuilt
+  $(eval tw := $(subst :, ,$(strip $(1)))) \
+  $(eval include $(CLEAR_VARS)) \
+  $(eval LOCAL_MODULE := $(word 1,$(tw))) \
+  $(eval LOCAL_MODULE_TAGS := optional) \
+  $(eval LOCAL_MODULE_CLASS := JAVA_LIBRARIES) \
+  $(eval LOCAL_SRC_FILES := $(word 2,$(tw))) \
+  $(eval LOCAL_UNINSTALLABLE_MODULE := true) \
+  $(eval LOCAL_SDK_VERSION := current) \
+  $(eval include $(BUILD_PREBUILT))
+endef
 
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
-    lib-exoplayer:libs/exoplayer.jar \
-    lib-exoplayer-v2:libs/exoplayer_v2.jar \
-    lib-exoplayer-v2-ext-ffmpeg:libs/exoplayer_v2_ext_ffmpeg.jar \
+$(foreach p,$(prebuilts),\
+  $(call define-prebuilt,$(p)))
 
-
-include $(BUILD_MULTI_PREBUILT)
-
+prebuilts :=
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8770301..3456f16 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -13,15 +13,19 @@
   ~ 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.
-  -->
-
+-->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.tv" xmlns:tools="http://schemas.android.com/tools">
+    package="com.android.tv" >
 
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-sdk
+        android:minSdkVersion="23"
+        android:targetSdkVersion="26" />
+
+    <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.GLOBAL_SEARCH" tools:ignore="ProtectedPermissions"/>
+    <uses-permission android:name="android.permission.GLOBAL_SEARCH" />
+    <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" />
@@ -32,187 +36,47 @@
     <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-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:protectionLevel="signatureOrSystem"
-        android:label="@string/permlab_receiveInputEvent"
+    <permission
+        android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT"
         android:description="@string/permdesc_receiveInputEvent"
-        tools:ignore="SignatureOrSystemPermissions"/>
+        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:protectionLevel="signatureOrSystem"
-        android:label="@string/permlab_customizeTvApp"
+    <permission
+        android:name="com.android.tv.permission.CUSTOMIZE_TV_APP"
         android:description="@string/permdesc_customizeTvApp"
-        tools:ignore="SignatureOrSystemPermissions"/>
+        android:label="@string/permlab_customizeTvApp"
+        android:protectionLevel="signatureOrSystem" />
 
-    <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/>
-
-    <application android:label="@string/app_name"
-        android:name=".TvApplication"
+    <application
+        android:name="com.android.tv.app.LiveTvApplication"
         android:allowBackup="true"
-        android:icon="@drawable/ic_launcher"
         android:banner="@drawable/banner"
+        android:icon="@drawable/ic_live_channels"
+        android:label="@string/app_name"
         android:supportsRtl="true"
-        android:theme="@style/Theme.TV">
-        <activity android:name="com.android.tv.TvActivity">
-            <intent-filter>
-                <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:screenOrientation="landscape"
-            android:launchMode="singleTask"
-            android:resizeableActivity="true"
-            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=".LauncherActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
-
-        <activity android:name=".SetupPassthroughActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            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=".SelectInputActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:launchMode="singleTask"
-            android:theme="@style/Theme.SelectInputActivity" />
-
-        <activity android:name=".onboarding.OnboardingActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:launchMode="singleTop"
-            android:theme="@style/Theme.Setup.GuidedStep" />
-
-        <activity android:name=".dvr.ui.browse.DvrBrowseActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:launchMode="singleTask"
-            android:theme="@style/Theme.Leanback.Browse">
-            <intent-filter>
-                <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=".dvr.ui.playback.DvrPlaybackActivity"
-            android:configChanges="keyboard|keyboardHidden|screenSize|smallestScreenSize|screenLayout|orientation"
-            android:launchMode="singleTask"
-            android:theme="@style/Theme.Leanback">
-            <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=".dvr.ui.browse.DvrDetailsActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@style/Theme.TV.Dvr.Browse.Details" />
-
-        <activity android:name=".dvr.ui.DvrSeriesSettingsActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@style/Theme.TV.Dvr.Series.Settings.GuidedStep" />
-
-        <activity android:name=".dvr.ui.DvrSeriesDeletionActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@style/Theme.TV.Dvr.Series.Deletion.GuidedStep" />
-
-        <activity android:name=".dvr.ui.DvrSeriesScheduledDialogActivity"
-            android:theme="@style/Theme.TV.dialog.HalfSizedDialog"/>
-
-        <activity android:name=".dvr.ui.list.DvrSchedulesActivity"
-            android:configChanges="keyboard|keyboardHidden"
-            android:theme="@style/Theme.Leanback.Details" />
-
-        <provider android:name="com.android.tv.search.LocalSearchProvider"
-            android:authorities="com.android.tv.search"
-            android:exported="true"
-            android:enabled="true" tools:ignore="ExportedContentProvider">
-            <meta-data android:name="SupportedSwitchActionType" android:value="CHANNEL|TVINPUT" />
-        </provider>
-
-        <service android:name="com.android.tv.recommendation.NotificationService"
-             android:exported="false" />
-        <service android:name=".recommendation.ChannelPreviewUpdater$ChannelPreviewUpdateService"
-             android:permission="android.permission.BIND_JOB_SERVICE" />
-
-        <receiver android:name="com.android.tv.receiver.BootCompletedReceiver">
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-        </receiver>
-        <receiver android:name="com.android.tv.receiver.PackageIntentsReceiver">
-            <intent-filter>
-                <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_REMOVED" />
-                <data android:scheme="package"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED" />
-            </intent-filter>
-        </receiver>
-        <receiver android:name="com.android.tv.receiver.GlobalKeyReceiver">
-            <intent-filter>
-                <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. -->
-            <intent-filter>
-                <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" />
-        </receiver>
-
-        <!-- USB tuner components definition -->
-        <activity android:name="com.android.tv.tuner.setup.TunerSetupActivity"
+        android:theme="@style/Theme.TV" >
+        <activity
+            android:name="com.android.tv.tuner.setup.LiveTvTunerSetupActivity"
             android:configChanges="keyboard|keyboardHidden"
             android:label="@string/bt_app_name"
             android:launchMode="singleInstance"
@@ -223,42 +87,190 @@
             </intent-filter>
         </activity>
 
-        <service android:name=".tuner.tvinput.TunerTvInputService"
-            android:enabled="false"
-            android:process="com.android.tv.tuner"
-            android:label="@string/bt_app_name"
-            android:permission="android.permission.BIND_TV_INPUT" >
+        <!-- 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>
+        <provider
+            android:name="com.android.tv.common.CommonPreferenceProvider"
+            android:authorities="com.android.tv.common.preferences"
+            android:exported="false"
+            android:process="com.android.tv.common" />
+
+        <activity android:name="com.android.tv.TvActivity" >
             <intent-filter>
-                <action android:name="android.media.tv.TvInputService" />
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
             </intent-filter>
-            <meta-data android:name="android.media.tv.input"
-                android:resource="@xml/ut_tvinputservice" />
-        </service>
-        <service android:name=".tuner.exoplayer.ffmpeg.FfmpegDecoderService"
-            android:isolatedProcess="true"
-            android:process="com.android.tv.ffmpeg" >
+        </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=".tuner.exoplayer.ffmpeg.IFfmpegDecoder" />
+                <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>
-        </service>
-        <provider android:name=".tuner.TunerPreferenceProvider"
-            android:authorities="com.android.tv.tuner.preferences"
-            android:process="com.android.tv.tuner"
-            android:exported="false" />
-        <!-- System initial setup component definition -->
-        <activity android:name=".setup.SystemSetupActivity"
-                  android:configChanges="keyboard|keyboardHidden"
-                  android:label="@string/bt_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="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.DEFAULT" />
             </intent-filter>
         </activity>
-        <!-- TunerInputController should be the same process with MainActivity to check status of MainActivity -->
-        <receiver android:name=".tuner.TunerInputController$IntentReceiver"
-            android:exported="false">
+        <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" >
+            <intent-filter>
+                <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" >
+            <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.dvr.ui.browse.DvrDetailsActivity"
+            android:configChanges="keyboard|keyboardHidden"
+            android:theme="@style/Theme.TV.Dvr.Browse.Details" />
+        <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" >
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name="com.android.tv.receiver.PackageIntentsReceiver" >
+            <intent-filter>
+                <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_REMOVED" />
+
+                <data android:scheme="package" />
+            </intent-filter>
+            <intent-filter>
+                <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/bt_app_name"
+            android:launchMode="singleInstance"
+            android:theme="@style/Theme.Setup.GuidedStep" >
+            <intent-filter>
+                <action android:name="com.android.tv.action.LAUNCH_SYSTEM_SETUP" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <!--
+          TunerInputController should be the same process with MainActivity to check status
+          of MainActivity
+        -->
+        <receiver
+            android:name="com.android.tv.tuner.TunerInputController$IntentReceiver"
+            android:exported="false" >
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
                 <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
@@ -267,20 +279,56 @@
                 <action android:name="com.android.tv.action.NETWORK_TUNER_ATTACHED" />
                 <action android:name="com.android.tv.action.NETWORK_TUNER_DETACHED" />
             </intent-filter>
-        </receiver>
+        </receiver> <!-- DVR -->
+        <service
+            android:name="com.android.tv.dvr.recorder.DvrRecordingService"
+            android:label="@string/dvr_service_name" />
 
-        <!-- DVR -->
-        <service android:name=".dvr.recorder.DvrRecordingService" android:label="@string/dvr_service_name" />
-        <receiver android:name=".dvr.recorder.DvrStartRecordingReceiver" />
+        <receiver android:name="com.android.tv.dvr.recorder.DvrStartRecordingReceiver" />
 
-        <service android:name=".tuner.tvinput.TunerStorageCleanUpService"
+        <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"
-            android:exported="true" />
-
-        <service android:name=".data.epg.EpgFetcher$EpgFetchService"
+            android:process="com.android.tv.tuner" />
+        <service
+            android:name="com.android.tv.data.epg.EpgFetchService"
             android:permission="android.permission.BIND_JOB_SERVICE" />
 
+        <receiver
+            android:name="com.android.tv.livetv.receiver.GlobalKeyReceiver"
+            android:exported="true" >
+            <intent-filter>
+                <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.
+            -->
+            <intent-filter>
+                <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" />
+        </receiver>
+
+        <service
+            android:name="com.android.tv.tuner.livetuner.LiveTvTunerTvInputService"
+            android:enabled="false"
+            android:label="@string/bt_app_name"
+            android:permission="android.permission.BIND_TV_INPUT"
+            android:process="com.android.tv.tuner" >
+            <intent-filter>
+                <action android:name="android.media.tv.TvInputService" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.media.tv.input"
+                android:resource="@xml/ut_tvinputservice" />
+        </service>
     </application>
-</manifest>
+
+</manifest>
\ No newline at end of file
diff --git a/LiveChannelsAndroidStyle.xml b/LiveChannelsAndroidStyle.xml
deleted file mode 100644
index a2627ce..0000000
--- a/LiveChannelsAndroidStyle.xml
+++ /dev/null
@@ -1,280 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 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.
-  -->
-
-<code_scheme name="LiveChannelsAndroidStyle">
-  <option name="JAVA_INDENT_OPTIONS">
-    <value>
-      <option name="INDENT_SIZE" value="4" />
-      <option name="CONTINUATION_INDENT_SIZE" value="8" />
-      <option name="TAB_SIZE" value="8" />
-      <option name="USE_TAB_CHARACTER" value="false" />
-      <option name="SMART_TABS" value="false" />
-      <option name="LABEL_INDENT_SIZE" value="0" />
-      <option name="LABEL_INDENT_ABSOLUTE" value="false" />
-      <option name="USE_RELATIVE_INDENTS" value="false" />
-    </value>
-  </option>
-  <option name="FIELD_NAME_PREFIX" value="m" />
-  <option name="STATIC_FIELD_NAME_PREFIX" value="s" />
-  <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
-  <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
-  <option name="IMPORT_LAYOUT_TABLE">
-    <value>
-      <package name="" withSubpackages="true" static="true" />
-      <emptyLine />
-      <package name="android" withSubpackages="true" static="false" />
-      <emptyLine />
-      <package name="com.google" withSubpackages="true" static="false" />
-      <emptyLine />
-      <package name="com" withSubpackages="true" static="false" />
-      <emptyLine />
-      <package name="junit" withSubpackages="true" static="false" />
-      <emptyLine />
-      <package name="net" withSubpackages="true" static="false" />
-      <emptyLine />
-      <package name="org" withSubpackages="true" static="false" />
-      <emptyLine />
-      <package name="java" withSubpackages="true" static="false" />
-      <emptyLine />
-      <package name="javax" withSubpackages="true" static="false" />
-      <emptyLine />
-      <package name="" withSubpackages="true" static="false" />
-    </value>
-  </option>
-  <option name="RIGHT_MARGIN" value="100" />
-  <option name="JD_P_AT_EMPTY_LINES" value="false" />
-  <option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" />
-  <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
-  <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
-  <option name="JD_KEEP_EMPTY_RETURN" value="false" />
-  <option name="JD_PRESERVE_LINE_FEEDS" value="true" />
-  <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
-  <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-  <option name="BLANK_LINES_AROUND_FIELD" value="1" />
-  <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
-  <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-  <option name="ALIGN_MULTILINE_FOR" value="false" />
-  <option name="CALL_PARAMETERS_WRAP" value="1" />
-  <option name="METHOD_PARAMETERS_WRAP" value="1" />
-  <option name="EXTENDS_LIST_WRAP" value="1" />
-  <option name="THROWS_LIST_WRAP" value="1" />
-  <option name="EXTENDS_KEYWORD_WRAP" value="1" />
-  <option name="THROWS_KEYWORD_WRAP" value="1" />
-  <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
-  <option name="BINARY_OPERATION_WRAP" value="1" />
-  <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
-  <option name="TERNARY_OPERATION_WRAP" value="1" />
-  <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
-  <option name="FOR_STATEMENT_WRAP" value="1" />
-  <option name="ARRAY_INITIALIZER_WRAP" value="1" />
-  <option name="ASSIGNMENT_WRAP" value="1" />
-  <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
-  <option name="WRAP_COMMENTS" value="true" />
-  <option name="IF_BRACE_FORCE" value="3" />
-  <option name="DOWHILE_BRACE_FORCE" value="3" />
-  <option name="WHILE_BRACE_FORCE" value="3" />
-  <option name="FOR_BRACE_FORCE" value="3" />
-  <AndroidXmlCodeStyleSettings>
-    <option name="USE_CUSTOM_SETTINGS" value="true" />
-  </AndroidXmlCodeStyleSettings>
-  <Objective-C-extensions>
-    <option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
-    <option name="RELEASE_STYLE" value="IVAR" />
-    <option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
-    <file>
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
-    </file>
-    <class>
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
-      <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
-    </class>
-    <extensions>
-      <pair source="cpp" header="h" />
-      <pair source="c" header="h" />
-    </extensions>
-  </Objective-C-extensions>
-  <XML>
-    <option name="XML_KEEP_BLANK_LINES" value="1" />
-    <option name="XML_ALIGN_ATTRIBUTES" value="false" />
-    <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
-  </XML>
-  <codeStyleSettings language="CFML">
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-    <option name="ALIGN_MULTILINE_FOR" value="false" />
-    <option name="CALL_PARAMETERS_WRAP" value="1" />
-    <option name="METHOD_PARAMETERS_WRAP" value="1" />
-    <option name="BINARY_OPERATION_WRAP" value="1" />
-    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="TERNARY_OPERATION_WRAP" value="1" />
-    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
-    <option name="FOR_STATEMENT_WRAP" value="1" />
-    <option name="ASSIGNMENT_WRAP" value="1" />
-    <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-  </codeStyleSettings>
-  <codeStyleSettings language="CoffeeScript">
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-    <option name="METHOD_PARAMETERS_WRAP" value="1" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-  </codeStyleSettings>
-  <codeStyleSettings language="ECMA Script Level 4">
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-    <option name="ALIGN_MULTILINE_FOR" value="false" />
-    <option name="CALL_PARAMETERS_WRAP" value="1" />
-    <option name="METHOD_PARAMETERS_WRAP" value="1" />
-    <option name="EXTENDS_LIST_WRAP" value="1" />
-    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
-    <option name="BINARY_OPERATION_WRAP" value="1" />
-    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="TERNARY_OPERATION_WRAP" value="1" />
-    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
-    <option name="FOR_STATEMENT_WRAP" value="1" />
-    <option name="ARRAY_INITIALIZER_WRAP" value="1" />
-    <option name="ASSIGNMENT_WRAP" value="1" />
-    <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="WRAP_COMMENTS" value="true" />
-    <option name="IF_BRACE_FORCE" value="3" />
-    <option name="DOWHILE_BRACE_FORCE" value="3" />
-    <option name="WHILE_BRACE_FORCE" value="3" />
-    <option name="FOR_BRACE_FORCE" value="3" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-  </codeStyleSettings>
-  <codeStyleSettings language="Groovy">
-    <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="BLANK_LINES_AROUND_FIELD" value="1" />
-    <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
-    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-    <option name="ALIGN_MULTILINE_FOR" value="false" />
-    <option name="CALL_PARAMETERS_WRAP" value="1" />
-    <option name="METHOD_PARAMETERS_WRAP" value="1" />
-    <option name="EXTENDS_LIST_WRAP" value="1" />
-    <option name="THROWS_LIST_WRAP" value="1" />
-    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
-    <option name="THROWS_KEYWORD_WRAP" value="1" />
-    <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
-    <option name="BINARY_OPERATION_WRAP" value="1" />
-    <option name="TERNARY_OPERATION_WRAP" value="1" />
-    <option name="FOR_STATEMENT_WRAP" value="1" />
-    <option name="ASSIGNMENT_WRAP" value="1" />
-    <option name="IF_BRACE_FORCE" value="3" />
-    <option name="WHILE_BRACE_FORCE" value="3" />
-    <option name="FOR_BRACE_FORCE" value="3" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-  </codeStyleSettings>
-  <codeStyleSettings language="JAVA">
-    <option name="KEEP_LINE_BREAKS" value="false" />
-    <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-    <option name="ALIGN_MULTILINE_FOR" value="false" />
-    <option name="CALL_PARAMETERS_WRAP" value="1" />
-    <option name="METHOD_PARAMETERS_WRAP" value="1" />
-    <option name="EXTENDS_LIST_WRAP" value="1" />
-    <option name="THROWS_LIST_WRAP" value="1" />
-    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
-    <option name="THROWS_KEYWORD_WRAP" value="1" />
-    <option name="METHOD_CALL_CHAIN_WRAP" value="1" />
-    <option name="BINARY_OPERATION_WRAP" value="1" />
-    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="TERNARY_OPERATION_WRAP" value="1" />
-    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
-    <option name="FOR_STATEMENT_WRAP" value="1" />
-    <option name="ARRAY_INITIALIZER_WRAP" value="1" />
-    <option name="ASSIGNMENT_WRAP" value="1" />
-    <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="IF_BRACE_FORCE" value="3" />
-    <option name="DOWHILE_BRACE_FORCE" value="3" />
-    <option name="WHILE_BRACE_FORCE" value="3" />
-    <option name="FOR_BRACE_FORCE" value="3" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-    <indentOptions>
-      <option name="TAB_SIZE" value="8" />
-    </indentOptions>
-  </codeStyleSettings>
-  <codeStyleSettings language="JSON">
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-  </codeStyleSettings>
-  <codeStyleSettings language="JavaScript">
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-    <option name="ALIGN_MULTILINE_FOR" value="false" />
-    <option name="CALL_PARAMETERS_WRAP" value="1" />
-    <option name="METHOD_PARAMETERS_WRAP" value="1" />
-    <option name="BINARY_OPERATION_WRAP" value="1" />
-    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="TERNARY_OPERATION_WRAP" value="1" />
-    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
-    <option name="FOR_STATEMENT_WRAP" value="1" />
-    <option name="ARRAY_INITIALIZER_WRAP" value="1" />
-    <option name="ASSIGNMENT_WRAP" value="1" />
-    <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="WRAP_COMMENTS" value="true" />
-    <option name="IF_BRACE_FORCE" value="3" />
-    <option name="DOWHILE_BRACE_FORCE" value="3" />
-    <option name="WHILE_BRACE_FORCE" value="3" />
-    <option name="FOR_BRACE_FORCE" value="3" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-  </codeStyleSettings>
-  <codeStyleSettings language="SQL">
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-    <indentOptions>
-      <option name="CONTINUATION_INDENT_SIZE" value="8" />
-      <option name="TAB_SIZE" value="4" />
-    </indentOptions>
-  </codeStyleSettings>
-  <codeStyleSettings language="TypeScript">
-    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
-    <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-    <option name="ALIGN_MULTILINE_FOR" value="false" />
-    <option name="CALL_PARAMETERS_WRAP" value="1" />
-    <option name="METHOD_PARAMETERS_WRAP" value="1" />
-    <option name="EXTENDS_LIST_WRAP" value="1" />
-    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
-    <option name="BINARY_OPERATION_WRAP" value="1" />
-    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="TERNARY_OPERATION_WRAP" value="1" />
-    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
-    <option name="FOR_STATEMENT_WRAP" value="1" />
-    <option name="ARRAY_INITIALIZER_WRAP" value="1" />
-    <option name="ASSIGNMENT_WRAP" value="1" />
-    <option name="PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE" value="true" />
-    <option name="WRAP_COMMENTS" value="true" />
-    <option name="IF_BRACE_FORCE" value="3" />
-    <option name="DOWHILE_BRACE_FORCE" value="3" />
-    <option name="WHILE_BRACE_FORCE" value="3" />
-    <option name="FOR_BRACE_FORCE" value="3" />
-    <option name="PARENT_SETTINGS_INSTALLED" value="true" />
-  </codeStyleSettings>
-</code_scheme>
\ No newline at end of file
diff --git a/ResourceManifest.xml b/ResourceManifest.xml
new file mode 100644
index 0000000..a859327
--- /dev/null
+++ b/ResourceManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.tv" xmlns:tools="http://schemas.android.com/tools">
+    <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/>
+    <application />
+</manifest>
diff --git a/common/Android.mk b/common/Android.mk
index e1d10c7..48f969e 100644
--- a/common/Android.mk
+++ b/common/Android.mk
@@ -13,12 +13,16 @@
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
+LOCAL_JAVA_LIBRARIES := \
+    android-support-annotations
+
+LOCAL_DISABLE_RESOLVE_SUPPORT_LIBRARIES := true
+
 LOCAL_SHARED_ANDROID_LIBRARIES := \
-    android-support-annotations \
     android-support-compat \
     android-support-core-ui \
     android-support-v7-recyclerview \
-    android-support-v17-leanback \
+    android-support-v17-leanback
 
 LOCAL_MIN_SDK_VERSION := 23
 
diff --git a/common/AndroidManifest.xml b/common/AndroidManifest.xml
index 19fb2a0..c1c698c 100644
--- a/common/AndroidManifest.xml
+++ b/common/AndroidManifest.xml
@@ -17,6 +17,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.tv.common"
           android:versionCode="1">
-    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
+    <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="21"/>
     <application />
 </manifest>
diff --git a/common/BuildConfig.java.in b/common/BuildConfig.java.in
index f463134..10816db 100644
--- a/common/BuildConfig.java.in
+++ b/common/BuildConfig.java.in
@@ -4,5 +4,6 @@
 public final class BuildConfig {
     public static final boolean DEBUG = %DEBUG%;
     public static final boolean ENG = %ENG%;
+    public static final boolean NO_JNI_TEST = false;
     private BuildConfig() {}
 }
\ No newline at end of file
diff --git a/common/res/values/strings.xml b/common/res/values/strings.xml
index ec02913..1c8a990 100644
--- a/common/res/values/strings.xml
+++ b/common/res/values/strings.xml
@@ -18,6 +18,7 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="action_text_done">Done</string>
     <string name="action_text_skip">Skip</string>
+    <string name="action_text_retry">Retry</string>
     <!-- The episode display format with season and episode number used in series details view.
     "S" is an abbreviation for Season and "Ep." is an abbreviation for episode.  ":" is used as a separator.
     For example, "S1: Ep. 1"-->
@@ -34,6 +35,14 @@
     "Ep." is an abbreviation for episode.
     For example, "Ep. 1807 Headline News". -->
     <string name="display_episode_title_format_no_season_number">Ep. <xliff:g id="episode_number" example="1807">%1$s</xliff:g> <xliff:g id="episode_title" example="Headline News">%2$s</xliff:g></string>
+
+    <!-- The content description for an episode. It will be spoken by accessibility service when needed.
+    For example, "Season 1 Episode 1 Winter is coming". -->
+    <string name="content_description_episode_format">Season <xliff:g id="season_number" example="1">%1$s</xliff:g> Episode <xliff:g id="episode_number" example="1">%2$s</xliff:g> <xliff:g id="episode_title" example="Winter is Coming">%3$s</xliff:g></string>
+    <!-- The content description for an episode. It will be spoken by accessibility service when needed.
+    For example, "Episode 1807 Headline News". -->
+    <string name="content_description_episode_format_no_season_number">Episode <xliff:g id="episode_number" example="1807">%1$s</xliff:g> <xliff:g id="episode_title" example="Headline News">%2$s</xliff:g></string>
+
     <!-- Program title with season and episode number used in DVR card views.
     HTML tag <i> is a placeholder for styling episode number part.
     "S" is an abbreviation for Season and "Ep." is an abbreviation for episode.  ":" is used as a separator.
diff --git a/common/src/com/android/tv/common/BaseApplication.java b/common/src/com/android/tv/common/BaseApplication.java
new file mode 100644
index 0000000..71c9b4d
--- /dev/null
+++ b/common/src/com/android/tv/common/BaseApplication.java
@@ -0,0 +1,107 @@
+/*
+ * 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.common;
+
+import android.annotation.TargetApi;
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.StrictMode;
+import android.support.annotation.VisibleForTesting;
+import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.recording.RecordingStorageStatusManager;
+import com.android.tv.common.util.Clock;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.Debug;
+import com.android.tv.common.util.SystemProperties;
+
+/** The base application class for Live TV applications. */
+public abstract class BaseApplication extends Application implements BaseSingletons {
+    private RecordingStorageStatusManager mRecordingStorageStatusManager;
+
+    /**
+     * An instance of {@link BaseSingletons}. Note that this can be set directly only for the test
+     * purpose.
+     */
+    @VisibleForTesting public static BaseSingletons sSingletons;
+
+    /** Returns the {@link BaseSingletons} using the application context. */
+    public static BaseSingletons getSingletons(Context context) {
+        // STOP-SHIP: changing the method to protected once the Tuner application is created.
+        // No need to be "synchronized" because this doesn't create any instance.
+        if (sSingletons == null) {
+            sSingletons = (BaseSingletons) context.getApplicationContext();
+        }
+        return sSingletons;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Debug.getTimer(Debug.TAG_START_UP_TIMER).start();
+        Debug.getTimer(Debug.TAG_START_UP_TIMER)
+                .log("Start " + this.getClass().getSimpleName() + ".onCreate");
+        CommonPreferences.initialize(this);
+
+        // Only set StrictMode for ENG builds because the build server only produces userdebug
+        // builds.
+        if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
+            StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
+                    new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog();
+            // TODO(b/69565157): Turn penaltyDeath on for VMPolicy when tests are fixed.
+            StrictMode.VmPolicy.Builder vmPolicyBuilder =
+                    new StrictMode.VmPolicy.Builder().detectAll().penaltyLog();
+
+            if (!CommonUtils.isRunningInTest()) {
+                threadPolicyBuilder.penaltyDialog();
+            }
+            StrictMode.setThreadPolicy(threadPolicyBuilder.build());
+            StrictMode.setVmPolicy(vmPolicyBuilder.build());
+        }
+        if (CommonFeatures.DVR.isEnabled(this)) {
+            getRecordingStorageStatusManager();
+        }
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                // Fetch remote config
+                getRemoteConfig().fetch(null);
+                return null;
+            }
+        }.execute();
+    }
+
+    @Override
+    public Clock getClock() {
+        return Clock.SYSTEM;
+    }
+
+    /** Returns {@link RecordingStorageStatusManager}. */
+    @Override
+    @TargetApi(Build.VERSION_CODES.N)
+    public RecordingStorageStatusManager getRecordingStorageStatusManager() {
+        if (mRecordingStorageStatusManager == null) {
+            mRecordingStorageStatusManager = new RecordingStorageStatusManager(this);
+        }
+        return mRecordingStorageStatusManager;
+    }
+
+    @Override
+    public abstract Intent getTunerSetupIntent(Context context);
+}
diff --git a/common/src/com/android/tv/common/BaseSingletons.java b/common/src/com/android/tv/common/BaseSingletons.java
new file mode 100644
index 0000000..e735cdb
--- /dev/null
+++ b/common/src/com/android/tv/common/BaseSingletons.java
@@ -0,0 +1,35 @@
+/*
+ * 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.common;
+
+import android.content.Context;
+import android.content.Intent;
+import com.android.tv.common.config.api.RemoteConfig.HasRemoteConfig;
+import com.android.tv.common.recording.RecordingStorageStatusManager;
+import com.android.tv.common.util.Clock;
+
+/** Injection point for the base app */
+public interface BaseSingletons extends HasRemoteConfig {
+
+    Clock getClock();
+
+    RecordingStorageStatusManager getRecordingStorageStatusManager();
+
+    Intent getTunerSetupIntent(Context context);
+
+    String getEmbeddedTunerInputId();
+}
diff --git a/common/src/com/android/tv/common/BooleanSystemProperty.java b/common/src/com/android/tv/common/BooleanSystemProperty.java
index 21e575a..5436524 100644
--- a/common/src/com/android/tv/common/BooleanSystemProperty.java
+++ b/common/src/com/android/tv/common/BooleanSystemProperty.java
@@ -17,7 +17,6 @@
 package com.android.tv.common;
 
 import android.util.Log;
-
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
@@ -25,16 +24,15 @@
 /**
  * Lazy loaded boolean system property.
  *
- * <p>Set with  <code>adb shell setprop <em>key</em> <em>value</em></code> where:
- * Values 'n', 'no', '0', 'false' or 'off' are considered false.
- * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
- * (case sensitive). See <a href=
+ * <p>Set with <code>adb shell setprop <em>key</em> <em>value</em></code> where: Values 'n', 'no',
+ * '0', 'false' or 'off' are considered false. Values 'y', 'yes', '1', 'true' or 'on' are considered
+ * true. (case sensitive). See <a href=
  * "https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/SystemProperties.java"
  * >android.os.SystemProperties.getBoolean</a>.
  */
 public class BooleanSystemProperty {
-    private final static String TAG = "BooleanSystemProperty";
-    private final static boolean DEBUG = false;
+    private static final String TAG = "BooleanSystemProperty";
+    private static final boolean DEBUG = false;
     private static final List<BooleanSystemProperty> ALL_PROPERTIES = new ArrayList<>();
     private final boolean mDefaultValue;
     private final String mKey;
@@ -78,7 +76,7 @@
     }
 
     /**
-     * Clears the cached value.  The next call to getValue will check {@code
+     * Clears the cached value. The next call to getValue will check {@code
      * android.os.SystemProperties}.
      */
     public void reset() {
@@ -88,7 +86,7 @@
     /**
      * Returns the value of the system property.
      *
-     * <p>If the value is cached  get the value from {@code android.os.SystemProperties} with the
+     * <p>If the value is cached get the value from {@code android.os.SystemProperties} with the
      * default set in the constructor.
      */
     public boolean getValue() {
diff --git a/common/src/com/android/tv/common/CommonConstants.java b/common/src/com/android/tv/common/CommonConstants.java
new file mode 100644
index 0000000..ac379d1
--- /dev/null
+++ b/common/src/com/android/tv/common/CommonConstants.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 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.common;
+
+/** Constants for common use in apps and tests. */
+public final class CommonConstants {
+
+    public static final String BASE_PACKAGE =
+                    "com.android.tv";
+    /** A constant for the key of the extra data for the app linking intent. */
+    public static final String EXTRA_APP_LINK_CHANNEL_URI = "app_link_channel_uri";
+
+    private CommonConstants() {}
+}
diff --git a/src/com/android/tv/tuner/TunerPreferenceProvider.java b/common/src/com/android/tv/common/CommonPreferenceProvider.java
similarity index 74%
rename from src/com/android/tv/tuner/TunerPreferenceProvider.java
rename to common/src/com/android/tv/common/CommonPreferenceProvider.java
index 3a3561b..b0be303 100644
--- a/src/com/android/tv/tuner/TunerPreferenceProvider.java
+++ b/common/src/com/android/tv/common/CommonPreferenceProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.tv.tuner;
+package com.android.tv.common;
 
 import android.content.ContentProvider;
 import android.content.ContentValues;
@@ -27,11 +27,11 @@
 import android.net.Uri;
 
 /**
- * A content provider for storing the preferences. It's used across TV app and USB tuner TV input.
+ * A content provider for storing common preferences. It's used across TV app and tuner TV inputs.
  */
-public class TunerPreferenceProvider extends ContentProvider {
+public class CommonPreferenceProvider extends ContentProvider {
     /** The authority of the provider */
-    public static final String AUTHORITY = "com.android.tv.tuner.preferences";
+    public static final String AUTHORITY = CommonConstants.BASE_PACKAGE + ".common.preferences";
 
     private static final String PATH_PREFERENCES = "preferences";
 
@@ -54,31 +54,23 @@
 
     /**
      * Builds a Uri that points to a specific preference.
-
+     *
      * @param key a key of the preference to point to
      */
     public static Uri buildPreferenceUri(String key) {
         return Preferences.CONTENT_URI.buildUpon().appendPath(key).build();
     }
 
-    /**
-     * Columns definitions for the preferences table.
-     */
+    /** Columns definitions for the preferences table. */
     public interface Preferences {
 
-        /**
-         * The content:// style for the preferences table.
-         */
+        /** The content:// style for the preferences table. */
         Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH_PREFERENCES);
 
-        /**
-         * The MIME type of a directory of preferences.
-         */
+        /** The MIME type of a directory of preferences. */
         String CONTENT_TYPE = "vnd.android.cursor.dir/preferences";
 
-        /**
-         * The MIME type of a single preference.
-         */
+        /** The MIME type of a single preference. */
         String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/preferences";
 
         /**
@@ -114,10 +106,16 @@
 
         @Override
         public void onCreate(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE " + PREFERENCES_TABLE + " ("
-                    + Preferences._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
-                    + Preferences.COLUMN_KEY + " TEXT NOT NULL,"
-                    + Preferences.COLUMN_VALUE + " TEXT);");
+            db.execSQL(
+                    "CREATE TABLE "
+                            + PREFERENCES_TABLE
+                            + " ("
+                            + Preferences._ID
+                            + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+                            + Preferences.COLUMN_KEY
+                            + " TEXT NOT NULL,"
+                            + Preferences.COLUMN_VALUE
+                            + " TEXT);");
         }
 
         @Override
@@ -133,15 +131,26 @@
     }
 
     @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+    public Cursor query(
+            Uri uri,
+            String[] projection,
+            String selection,
+            String[] selectionArgs,
             String sortOrder) {
         int match = sUriMatcher.match(uri);
         if (match != MATCH_PREFERENCE && match != MATCH_PREFERENCE_KEY) {
             throw new UnsupportedOperationException();
         }
         SQLiteDatabase db = mDatabaseOpenHelper.getReadableDatabase();
-        Cursor cursor = db.query(PREFERENCES_TABLE, projection, selection, selectionArgs,
-                null, null, sortOrder);
+        Cursor cursor =
+                db.query(
+                        PREFERENCES_TABLE,
+                        projection,
+                        selection,
+                        selectionArgs,
+                        null,
+                        null,
+                        sortOrder);
         cursor.setNotificationUri(getContext().getContentResolver(), uri);
         return cursor;
     }
@@ -153,14 +162,15 @@
                 return Preferences.CONTENT_TYPE;
             case MATCH_PREFERENCE_KEY:
                 return Preferences.CONTENT_ITEM_TYPE;
+            default:
+                throw new IllegalArgumentException("Unknown URI " + uri);
         }
-        throw new IllegalArgumentException("Unknown URI " + uri);
     }
 
     /**
      * Inserts a preference row into the preference table.
      *
-     * If a key is already exists in the table, it removes the old row and inserts a new row.
+     * <p>If a key is already exists in the table, it removes the old row and inserts a new row.
      *
      * @param uri the URL of the table to insert into
      * @param values the initial values for the newly inserted row
@@ -178,8 +188,10 @@
         SQLiteDatabase db = mDatabaseOpenHelper.getWritableDatabase();
 
         // Remove the old row.
-        db.delete(PREFERENCES_TABLE, Preferences.COLUMN_KEY + " like ?",
-                new String[]{values.getAsString(Preferences.COLUMN_KEY)});
+        db.delete(
+                PREFERENCES_TABLE,
+                Preferences.COLUMN_KEY + " like ?",
+                new String[] {values.getAsString(Preferences.COLUMN_KEY)});
 
         long rowId = db.insert(PREFERENCES_TABLE, null, values);
         if (rowId > 0) {
diff --git a/common/src/com/android/tv/common/CommonPreferences.java b/common/src/com/android/tv/common/CommonPreferences.java
new file mode 100644
index 0000000..5a94eec
--- /dev/null
+++ b/common/src/com/android/tv/common/CommonPreferences.java
@@ -0,0 +1,332 @@
+/*
+ * 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.common;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.GuardedBy;
+import android.support.annotation.IntDef;
+import android.support.annotation.MainThread;
+import android.util.Log;
+import com.android.tv.common.CommonPreferenceProvider.Preferences;
+import com.android.tv.common.util.CommonUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+
+/** A helper class for setting/getting common preferences across applications. */
+public class CommonPreferences {
+    private static final String TAG = "CommonPreferences";
+
+    private static final String PREFS_KEY_LAUNCH_SETUP = "launch_setup";
+    private static final String PREFS_KEY_STORE_TS_STREAM = "store_ts_stream";
+    private static final String PREFS_KEY_TRICKPLAY_SETTING = "trickplay_setting";
+    private static final String PREFS_KEY_LAST_POSTAL_CODE = "last_postal_code";
+
+    private static final Map<String, Class> sPref2TypeMapping = new HashMap<>();
+
+    static {
+        sPref2TypeMapping.put(PREFS_KEY_TRICKPLAY_SETTING, int.class);
+        sPref2TypeMapping.put(PREFS_KEY_STORE_TS_STREAM, boolean.class);
+        sPref2TypeMapping.put(PREFS_KEY_LAUNCH_SETUP, boolean.class);
+        sPref2TypeMapping.put(PREFS_KEY_LAST_POSTAL_CODE, String.class);
+    }
+
+    private static final String SHARED_PREFS_NAME =
+            CommonConstants.BASE_PACKAGE + ".common.preferences";
+
+    @IntDef({TRICKPLAY_SETTING_NOT_SET, TRICKPLAY_SETTING_DISABLED, TRICKPLAY_SETTING_ENABLED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TrickplaySetting {}
+
+    /** Trickplay setting is not changed by a user. Trickplay will be enabled in this case. */
+    public static final int TRICKPLAY_SETTING_NOT_SET = -1;
+
+    /** Trickplay setting is disabled. */
+    public static final int TRICKPLAY_SETTING_DISABLED = 0;
+
+    /** Trickplay setting is enabled. */
+    public static final int TRICKPLAY_SETTING_ENABLED = 1;
+
+    @GuardedBy("CommonPreferences.class")
+    private static final Bundle sPreferenceValues = new Bundle();
+
+    private static LoadPreferencesTask sLoadPreferencesTask;
+    private static ContentObserver sContentObserver;
+    private static CommonPreferencesChangedListener sPreferencesChangedListener = null;
+
+    protected static boolean sInitialized;
+
+    /** Listeners for CommonPreferences change. */
+    public interface CommonPreferencesChangedListener {
+        void onCommonPreferencesChanged();
+    }
+
+    /** Initializes the common preferences. */
+    @MainThread
+    public static void initialize(final Context context) {
+        if (sInitialized) {
+            return;
+        }
+        sInitialized = true;
+        if (useContentProvider(context)) {
+            loadPreferences(context);
+            sContentObserver =
+                    new ContentObserver(new Handler()) {
+                        @Override
+                        public void onChange(boolean selfChange) {
+                            loadPreferences(context);
+                        }
+                    };
+            context.getContentResolver()
+                    .registerContentObserver(Preferences.CONTENT_URI, true, sContentObserver);
+        } else {
+            new AsyncTask<Void, Void, Void>() {
+                @Override
+                protected Void doInBackground(Void... params) {
+                    getSharedPreferences(context);
+                    return null;
+                }
+            }.execute();
+        }
+    }
+
+    /** Releases the resources. */
+    public static synchronized void release(Context context) {
+        if (useContentProvider(context) && sContentObserver != null) {
+            context.getContentResolver().unregisterContentObserver(sContentObserver);
+        }
+        setCommonPreferencesChangedListener(null);
+    }
+
+    /** Sets the listener for CommonPreferences change. */
+    public static void setCommonPreferencesChangedListener(
+            CommonPreferencesChangedListener listener) {
+        sPreferencesChangedListener = listener;
+    }
+
+    /**
+     * Loads the preferences from database.
+     *
+     * <p>This preferences is used across processes, so the preferences should be loaded again when
+     * the databases changes.
+     */
+    @MainThread
+    public static void loadPreferences(Context context) {
+        if (sLoadPreferencesTask != null
+                && sLoadPreferencesTask.getStatus() != AsyncTask.Status.FINISHED) {
+            sLoadPreferencesTask.cancel(true);
+        }
+        sLoadPreferencesTask = new LoadPreferencesTask(context);
+        sLoadPreferencesTask.execute();
+    }
+
+    private static boolean useContentProvider(Context context) {
+        // If TIS is a part of LC, it should use ContentProvider to resolve multiple process access.
+        return CommonUtils.isPackagedWithLiveChannels(context);
+    }
+
+    public static synchronized boolean shouldShowSetupActivity(Context context) {
+        SoftPreconditions.checkState(sInitialized);
+        if (useContentProvider(context)) {
+            return sPreferenceValues.getBoolean(PREFS_KEY_LAUNCH_SETUP);
+        } else {
+            return getSharedPreferences(context).getBoolean(PREFS_KEY_LAUNCH_SETUP, false);
+        }
+    }
+
+    public static synchronized void setShouldShowSetupActivity(Context context, boolean need) {
+        if (useContentProvider(context)) {
+            setPreference(context, PREFS_KEY_LAUNCH_SETUP, need);
+        } else {
+            getSharedPreferences(context).edit().putBoolean(PREFS_KEY_LAUNCH_SETUP, need).apply();
+        }
+    }
+
+    public static synchronized @TrickplaySetting int getTrickplaySetting(Context context) {
+        SoftPreconditions.checkState(sInitialized);
+        if (useContentProvider(context)) {
+            return sPreferenceValues.getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
+        } else {
+            return getSharedPreferences(context)
+                    .getInt(PREFS_KEY_TRICKPLAY_SETTING, TRICKPLAY_SETTING_NOT_SET);
+        }
+    }
+
+    public static synchronized void setTrickplaySetting(
+            Context context, @TrickplaySetting int trickplaySetting) {
+        SoftPreconditions.checkState(sInitialized);
+        SoftPreconditions.checkArgument(trickplaySetting != TRICKPLAY_SETTING_NOT_SET);
+        if (useContentProvider(context)) {
+            setPreference(context, PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting);
+        } else {
+            getSharedPreferences(context)
+                    .edit()
+                    .putInt(PREFS_KEY_TRICKPLAY_SETTING, trickplaySetting)
+                    .apply();
+        }
+    }
+
+    public static synchronized boolean getStoreTsStream(Context context) {
+        SoftPreconditions.checkState(sInitialized);
+        if (useContentProvider(context)) {
+            return sPreferenceValues.getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
+        } else {
+            return getSharedPreferences(context).getBoolean(PREFS_KEY_STORE_TS_STREAM, false);
+        }
+    }
+
+    public static synchronized void setStoreTsStream(Context context, boolean shouldStore) {
+        if (useContentProvider(context)) {
+            setPreference(context, PREFS_KEY_STORE_TS_STREAM, shouldStore);
+        } else {
+            getSharedPreferences(context)
+                    .edit()
+                    .putBoolean(PREFS_KEY_STORE_TS_STREAM, shouldStore)
+                    .apply();
+        }
+    }
+
+    public static synchronized String getLastPostalCode(Context context) {
+        SoftPreconditions.checkState(sInitialized);
+        if (useContentProvider(context)) {
+            return sPreferenceValues.getString(PREFS_KEY_LAST_POSTAL_CODE);
+        } else {
+            return getSharedPreferences(context).getString(PREFS_KEY_LAST_POSTAL_CODE, null);
+        }
+    }
+
+    public static synchronized void setLastPostalCode(Context context, String postalCode) {
+        if (useContentProvider(context)) {
+            setPreference(context, PREFS_KEY_LAST_POSTAL_CODE, postalCode);
+        } else {
+            getSharedPreferences(context)
+                    .edit()
+                    .putString(PREFS_KEY_LAST_POSTAL_CODE, postalCode)
+                    .apply();
+        }
+    }
+
+    protected static SharedPreferences getSharedPreferences(Context context) {
+        return context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+    }
+
+    private static synchronized void setPreference(Context context, String key, String value) {
+        sPreferenceValues.putString(key, value);
+        savePreference(context, key, value);
+    }
+
+    private static synchronized void setPreference(Context context, String key, int value) {
+        sPreferenceValues.putInt(key, value);
+        savePreference(context, key, Integer.toString(value));
+    }
+
+    private static synchronized void setPreference(Context context, String key, long value) {
+        sPreferenceValues.putLong(key, value);
+        savePreference(context, key, Long.toString(value));
+    }
+
+    private static synchronized void setPreference(Context context, String key, boolean value) {
+        sPreferenceValues.putBoolean(key, value);
+        savePreference(context, key, Boolean.toString(value));
+    }
+
+    private static void savePreference(
+            final Context context, final String key, final String value) {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                ContentResolver resolver = context.getContentResolver();
+                ContentValues values = new ContentValues();
+                values.put(Preferences.COLUMN_KEY, key);
+                values.put(Preferences.COLUMN_VALUE, value);
+                try {
+                    resolver.insert(Preferences.CONTENT_URI, values);
+                } catch (Exception e) {
+                    SoftPreconditions.warn(
+                            TAG, "setPreference", e, "Error writing preference values");
+                }
+                return null;
+            }
+        }.execute();
+    }
+
+    private static class LoadPreferencesTask extends AsyncTask<Void, Void, Bundle> {
+        private final Context mContext;
+
+        private LoadPreferencesTask(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        protected Bundle doInBackground(Void... params) {
+            Bundle bundle = new Bundle();
+            ContentResolver resolver = mContext.getContentResolver();
+            String[] projection = new String[] {Preferences.COLUMN_KEY, Preferences.COLUMN_VALUE};
+            try (Cursor cursor =
+                    resolver.query(Preferences.CONTENT_URI, projection, null, null, null)) {
+                if (cursor != null) {
+                    while (!isCancelled() && cursor.moveToNext()) {
+                        String key = cursor.getString(0);
+                        String value = cursor.getString(1);
+                        Class prefClass = sPref2TypeMapping.get(key);
+                        if (prefClass == int.class) {
+                            try {
+                                bundle.putInt(key, Integer.parseInt(value));
+                            } catch (NumberFormatException e) {
+                                Log.w(TAG, "Invalid format, key=" + key + ", value=" + value);
+                            }
+                        } else if (prefClass == long.class) {
+                            try {
+                                bundle.putLong(key, Long.parseLong(value));
+                            } catch (NumberFormatException e) {
+                                Log.w(TAG, "Invalid format, key=" + key + ", value=" + value);
+                            }
+                        } else if (prefClass == boolean.class) {
+                            bundle.putBoolean(key, Boolean.parseBoolean(value));
+                        } else {
+                            bundle.putString(key, value);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                SoftPreconditions.warn(TAG, "getPreference", e, "Error querying preference values");
+                return null;
+            }
+            return bundle;
+        }
+
+        @Override
+        protected void onPostExecute(Bundle bundle) {
+            synchronized (CommonPreferences.class) {
+                if (bundle != null) {
+                    sPreferenceValues.putAll(bundle);
+                }
+            }
+            if (sPreferencesChangedListener != null) {
+                sPreferencesChangedListener.onCommonPreferencesChanged();
+            }
+        }
+    }
+}
diff --git a/common/src/com/android/tv/common/SoftPreconditions.java b/common/src/com/android/tv/common/SoftPreconditions.java
index 823c42f..3b0510d 100644
--- a/common/src/com/android/tv/common/SoftPreconditions.java
+++ b/common/src/com/android/tv/common/SoftPreconditions.java
@@ -17,17 +17,18 @@
 package com.android.tv.common;
 
 import android.content.Context;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.Log;
-
 import com.android.tv.common.feature.Feature;
+import com.android.tv.common.util.CommonUtils;
 
 /**
- * Simple static methods to be called at the start of your own methods to verify
- * correct arguments and state.
+ * Simple static methods to be called at the start of your own methods to verify correct arguments
+ * and state.
  *
- * <p>{@code checkXXX} methods throw exceptions when {@link BuildConfig#ENG} is true, and
- * logs a warning when it is false.
+ * <p>{@code checkXXX} methods throw exceptions when {@link BuildConfig#ENG} is true, and logs a
+ * warning when it is false.
  *
  * <p>This is based on com.android.internal.util.Preconditions.
  */
@@ -35,26 +36,35 @@
     private static final String TAG = "SoftPreconditions";
 
     /**
-     * Throws or logs if an expression involving the parameter of the calling
-     * method is not true.
+     * Throws or logs if an expression involving the parameter of the calling method is not true.
      *
      * @param expression a boolean expression
-     * @param tag Used to identify the source of a log message.  It usually
-     *            identifies the class or activity where the log call occurs.
-     * @param msg The message you would like logged.
+     * @param tag Used to identify the source of a log message. It usually identifies the class or
+     *     activity where the log call occurs.
+     * @param errorMessageTemplate a template for the exception message should the check fail. The
+     *     message is formed by replacing each {@code %s} placeholder in the template with an
+     *     argument. These are matched by position - the first {@code %s} gets {@code
+     *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message
+     *     in square braces. Unmatched placeholders will be left as-is.
+     * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
+     *     are converted to strings using {@link String#valueOf(Object)}.
      * @return the evaluation result of the boolean expression
      * @throws IllegalArgumentException if {@code expression} is true
      */
-    public static boolean checkArgument(final boolean expression, String tag, String msg) {
+    public static boolean checkArgument(
+            final boolean expression,
+            String tag,
+            @Nullable String errorMessageTemplate,
+            @Nullable Object... errorMessageArgs) {
         if (!expression) {
-            warn(tag, "Illegal argument", msg, new IllegalArgumentException(msg));
+            String msg = format(errorMessageTemplate, errorMessageArgs);
+            warn(tag, "Illegal argument", new IllegalArgumentException(msg), msg);
         }
         return expression;
     }
 
     /**
-     * Throws or logs if an expression involving the parameter of the calling
-     * method is not true.
+     * Throws or logs if an expression involving the parameter of the calling method is not true.
      *
      * @param expression a boolean expression
      * @return the evaluation result of the boolean expression
@@ -69,15 +79,26 @@
      * Throws or logs if an and object is null.
      *
      * @param reference an object reference
-     * @param tag Used to identify the source of a log message.  It usually
-     *            identifies the class or activity where the log call occurs.
-     * @param msg The message you would like logged.
+     * @param tag Used to identify the source of a log message. It usually identifies the class or
+     *     activity where the log call occurs.
+     * @param errorMessageTemplate a template for the exception message should the check fail. The
+     *     message is formed by replacing each {@code %s} placeholder in the template with an
+     *     argument. These are matched by position - the first {@code %s} gets {@code
+     *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message
+     *     in square braces. Unmatched placeholders will be left as-is.
+     * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
+     *     are converted to strings using {@link String#valueOf(Object)}.
      * @return true if the object is null
      * @throws NullPointerException if {@code reference} is null
      */
-    public static <T> T checkNotNull(final T reference, String tag, String msg) {
+    public static <T> T checkNotNull(
+            final T reference,
+            String tag,
+            @Nullable String errorMessageTemplate,
+            @Nullable Object... errorMessageArgs) {
         if (reference == null) {
-            warn(tag, "Null Pointer", msg, new NullPointerException(msg));
+            String msg = format(errorMessageTemplate, errorMessageArgs);
+            warn(tag, "Null Pointer", new NullPointerException(msg), msg);
         }
         return reference;
     }
@@ -94,26 +115,37 @@
     }
 
     /**
-     * Throws or logs if an expression involving the state of the calling
-     * instance, but not involving any parameters to the calling method is not true.
+     * Throws or logs if an expression involving the state of the calling instance, but not
+     * involving any parameters to the calling method is not true.
      *
      * @param expression a boolean expression
-     * @param tag Used to identify the source of a log message.  It usually
-     *            identifies the class or activity where the log call occurs.
-     * @param msg The message you would like logged.
+     * @param tag Used to identify the source of a log message. It usually identifies the class or
+     *     activity where the log call occurs.
+     * @param errorMessageTemplate a template for the exception message should the check fail. The
+     *     message is formed by replacing each {@code %s} placeholder in the template with an
+     *     argument. These are matched by position - the first {@code %s} gets {@code
+     *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message
+     *     in square braces. Unmatched placeholders will be left as-is.
+     * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
+     *     are converted to strings using {@link String#valueOf(Object)}.
      * @return the evaluation result of the boolean expression
      * @throws IllegalStateException if {@code expression} is true
      */
-    public static boolean checkState(final boolean expression, String tag, String msg) {
+    public static boolean checkState(
+            final boolean expression,
+            String tag,
+            @Nullable String errorMessageTemplate,
+            @Nullable Object... errorMessageArgs) {
         if (!expression) {
-            warn(tag, "Illegal State", msg, new IllegalStateException(msg));
+            String msg = format(errorMessageTemplate, errorMessageArgs);
+            warn(tag, "Illegal State", new IllegalStateException(msg), msg);
         }
         return expression;
     }
 
     /**
-     * Throws or logs if an expression involving the state of the calling
-     * instance, but not involving any parameters to the calling method is not true.
+     * Throws or logs if an expression involving the state of the calling instance, but not
+     * involving any parameters to the calling method is not true.
      *
      * @param expression a boolean expression
      * @return the evaluation result of the boolean expression
@@ -129,8 +161,8 @@
      *
      * @param context an android context
      * @param feature the required feature
-     * @param tag used to identify the source of a log message.  It usually
-     *            identifies the class or activity where the log call occurs
+     * @param tag used to identify the source of a log message. It usually identifies the class or
+     *     activity where the log call occurs
      * @throws IllegalStateException if {@code feature} is not enabled
      */
     public static void checkFeatureEnabled(Context context, Feature feature, String tag) {
@@ -138,14 +170,15 @@
     }
 
     /**
-     * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true, else log a warning.
+     * Throws a {@link RuntimeException} if {@link BuildConfig#ENG} is true and not running in a
+     * test, else log a warning.
      *
-     * @param tag Used to identify the source of a log message.  It usually
-     *            identifies the class or activity where the log call occurs.
-     * @param msg The message you would like logged
+     * @param tag Used to identify the source of a log message. It usually identifies the class or
+     *     activity where the log call occurs.
      * @param e The exception to wrap with a RuntimeException when thrown.
+     * @param msg The message to be logged
      */
-    public static void warn(String tag, String prefix, String msg, Exception e)
+    public static void warn(String tag, String prefix, Exception e, String msg)
             throws RuntimeException {
         if (TextUtils.isEmpty(tag)) {
             tag = TAG;
@@ -159,13 +192,57 @@
             logMessage = prefix + ": " + msg;
         }
 
-        if (BuildConfig.ENG) {
+        if (BuildConfig.ENG && !CommonUtils.isRunningInTest()) {
             throw new RuntimeException(msg, e);
         } else {
             Log.w(tag, logMessage, e);
         }
     }
 
-    private SoftPreconditions() {
+    /**
+     * Substitutes each {@code %s} in {@code template} with an argument. These are matched by
+     * position: the first {@code %s} gets {@code args[0]}, etc. If there are more arguments than
+     * placeholders, the unmatched arguments will be appended to the end of the formatted message in
+     * square braces.
+     *
+     * @param template a string containing 0 or more {@code %s} placeholders. null is treated as
+     *     "null".
+     * @param args the arguments to be substituted into the message template. Arguments are
+     *     converted to strings using {@link String#valueOf(Object)}. Arguments can be null.
+     */
+    static String format(@Nullable String template, @Nullable Object... args) {
+        template = String.valueOf(template); // null -> "null"
+
+        args = args == null ? new Object[] {"(Object[])null"} : args;
+
+        // start substituting the arguments into the '%s' placeholders
+        StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
+        int templateStart = 0;
+        int i = 0;
+        while (i < args.length) {
+            int placeholderStart = template.indexOf("%s", templateStart);
+            if (placeholderStart == -1) {
+                break;
+            }
+            builder.append(template, templateStart, placeholderStart);
+            builder.append(args[i++]);
+            templateStart = placeholderStart + 2;
+        }
+        builder.append(template, templateStart, template.length());
+
+        // if we run out of placeholders, append the extra args in square braces
+        if (i < args.length) {
+            builder.append(" [");
+            builder.append(args[i++]);
+            while (i < args.length) {
+                builder.append(", ");
+                builder.append(args[i++]);
+            }
+            builder.append(']');
+        }
+
+        return builder.toString();
     }
+
+    private SoftPreconditions() {}
 }
diff --git a/common/src/com/android/tv/common/TvCommonConstants.java b/common/src/com/android/tv/common/TvCommonConstants.java
deleted file mode 100644
index 9824497..0000000
--- a/common/src/com/android/tv/common/TvCommonConstants.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2015 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.common;
-
-import android.media.tv.TvInputInfo;
-import android.os.Build;
-
-/**
- * Constants for common use in TV app and tests.
- */
-public final class TvCommonConstants {
-    /**
-     * A constant for the key of the extra data for the app linking intent.
-     */
-    public static final String EXTRA_APP_LINK_CHANNEL_URI = "app_link_channel_uri";
-
-    /**
-     * An intent action to launch setup activity of a TV input. The intent should include
-     * TV input ID in the value of {@link EXTRA_INPUT_ID}. Optionally, given the value of
-     * {@link EXTRA_ACTIVITY_AFTER_COMPLETION}, the activity will be launched after the setup
-     * activity successfully finishes.
-     */
-    public static final String INTENT_ACTION_INPUT_SETUP =
-            "com.android.tv.action.LAUNCH_INPUT_SETUP";
-
-    /**
-     * A constant of the key to indicate a TV input ID for the intent action
-     * {@link INTENT_ACTION_INPUT_SETUP}.
-     *
-     * <p>Value type: String
-     */
-    public static final String EXTRA_INPUT_ID = TvInputInfo.EXTRA_INPUT_ID;
-
-    /**
-     * A constant of the key for intent to launch actual TV input setup activity used with
-     * {@link INTENT_ACTION_INPUT_SETUP}.
-     *
-     * <p>Value type: Intent (Parcelable)
-     */
-    public static final String EXTRA_SETUP_INTENT =
-            "com.android.tv.extra.SETUP_INTENT";
-
-    /**
-     * A constant of the key to indicate an Activity launch intent for the intent action
-     * {@link INTENT_ACTION_INPUT_SETUP}.
-     *
-     * <p>Value type: Intent (Parcelable)
-     */
-    public static final String EXTRA_ACTIVITY_AFTER_COMPLETION =
-            "com.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION";
-
-    private TvCommonConstants() {
-    }
-}
diff --git a/common/src/com/android/tv/common/TvCommonUtils.java b/common/src/com/android/tv/common/TvCommonUtils.java
deleted file mode 100644
index c391ad2..0000000
--- a/common/src/com/android/tv/common/TvCommonUtils.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2015 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.common;
-
-import android.content.Intent;
-import android.media.tv.TvInputInfo;
-
-/**
- * Util class for common use in TV app and inputs.
- */
-public final class TvCommonUtils {
-    private static Boolean sRunningInTest;
-
-    private TvCommonUtils() { }
-
-    /**
-     * Returns an intent to start the setup activity for the TV input using {@link
-     * TvCommonConstants#INTENT_ACTION_INPUT_SETUP}.
-     */
-    public static Intent createSetupIntent(Intent originalSetupIntent, String inputId) {
-        if (originalSetupIntent == null) {
-            return null;
-        }
-        Intent setupIntent = new Intent(originalSetupIntent);
-        if (!TvCommonConstants.INTENT_ACTION_INPUT_SETUP.equals(originalSetupIntent.getAction())) {
-            Intent intentContainer = new Intent(TvCommonConstants.INTENT_ACTION_INPUT_SETUP);
-            intentContainer.putExtra(TvCommonConstants.EXTRA_SETUP_INTENT, originalSetupIntent);
-            intentContainer.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId);
-            setupIntent = intentContainer;
-        }
-        return setupIntent;
-    }
-
-    /**
-     * Returns an intent to start the setup activity for this TV input using {@link
-     * TvCommonConstants#INTENT_ACTION_INPUT_SETUP}.
-     */
-    public static Intent createSetupIntent(TvInputInfo input) {
-        return createSetupIntent(input.createSetupIntent(), input.getId());
-    }
-
-    /**
-     * Checks if this application is running in tests.
-     *
-     * <p>{@link android.app.ActivityManager#isRunningInTestHarness} doesn't return {@code true} for
-     * the usual devices even the application is running in tests. We need to figure it out by
-     * checking whether the class in tv-tests-common module can be loaded or not.
-     */
-    public static synchronized boolean isRunningInTest() {
-        if (sRunningInTest == null) {
-            try {
-                Class.forName("com.android.tv.testing.Utils");
-                sRunningInTest = true;
-            } catch (ClassNotFoundException e) {
-                sRunningInTest = false;
-            }
-        }
-        return sRunningInTest;
-    }
-}
diff --git a/common/src/com/android/tv/common/TvContentRatingCache.java b/common/src/com/android/tv/common/TvContentRatingCache.java
index 8b3c06f..cfdb8e4 100644
--- a/common/src/com/android/tv/common/TvContentRatingCache.java
+++ b/common/src/com/android/tv/common/TvContentRatingCache.java
@@ -22,7 +22,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
-
+import com.android.tv.common.memory.MemoryManageable;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -31,13 +31,11 @@
 import java.util.SortedSet;
 import java.util.TreeSet;
 
-/**
- * TvContentRating cache.
- */
+/** TvContentRating cache. */
 public final class TvContentRatingCache implements MemoryManageable {
-    private final static String TAG = "TvContentRatings";
+    private static final String TAG = "TvContentRatings";
 
-    private final static TvContentRatingCache INSTANCE = new TvContentRatingCache();
+    private static final TvContentRatingCache INSTANCE = new TvContentRatingCache();
 
     public static TvContentRatingCache getInstance() {
         return INSTANCE;
@@ -48,8 +46,8 @@
 
     /**
      * Returns an array TvContentRatings from a string of comma separated set of rating strings
-     * creating each from {@link TvContentRating#unflattenFromString(String)} if needed.
-     * Returns {@code null} if the string is empty or contains no valid ratings.
+     * creating each from {@link TvContentRating#unflattenFromString(String)} if needed. Returns
+     * {@code null} if the string is empty or contains no valid ratings.
      */
     @Nullable
     public synchronized TvContentRating[] getRatings(String commaSeparatedRatings) {
@@ -60,8 +58,8 @@
         if (mRatingsMultiMap.containsKey(commaSeparatedRatings)) {
             tvContentRatings = mRatingsMultiMap.get(commaSeparatedRatings);
         } else {
-            String normalizedRatings = TextUtils
-                    .join(",", getSortedSetFromCsv(commaSeparatedRatings));
+            String normalizedRatings =
+                    TextUtils.join(",", getSortedSetFromCsv(commaSeparatedRatings));
             if (mRatingsMultiMap.containsKey(normalizedRatings)) {
                 tvContentRatings = mRatingsMultiMap.get(normalizedRatings);
             } else {
@@ -76,9 +74,7 @@
         return tvContentRatings;
     }
 
-    /**
-     * Returns a sorted array of TvContentRatings from a comma separated string of ratings.
-     */
+    /** Returns a sorted array of TvContentRatings from a comma separated string of ratings. */
     @VisibleForTesting
     static TvContentRating[] stringToContentRatings(String commaSeparatedRatings) {
         if (TextUtils.isEmpty(commaSeparatedRatings)) {
@@ -93,8 +89,9 @@
                 Log.e(TAG, "Can't parse the content rating: '" + rating + "'", e);
             }
         }
-        return contentRatings.size() == 0 ?
-                null : contentRatings.toArray(new TvContentRating[contentRatings.size()]);
+        return contentRatings.size() == 0
+                ? null
+                : contentRatings.toArray(new TvContentRating[contentRatings.size()]);
     }
 
     private static Set<String> getSortedSetFromCsv(String commaSeparatedRatings) {
@@ -118,8 +115,8 @@
     }
 
     /**
-     * Returns a string of each flattened content rating, sorted and concatenated together
-     * with a comma.
+     * Returns a string of each flattened content rating, sorted and concatenated together with a
+     * comma.
      */
     public static String contentRatingsToString(TvContentRating[] contentRatings) {
         if (contentRatings == null || contentRatings.length == 0) {
@@ -141,6 +138,5 @@
         mRatingsMultiMap.clear();
     }
 
-    private TvContentRatingCache() {
-    }
+    private TvContentRatingCache() {}
 }
diff --git a/common/src/com/android/tv/common/WeakHandler.java b/common/src/com/android/tv/common/WeakHandler.java
index 188f20e..dbdd476 100644
--- a/common/src/com/android/tv/common/WeakHandler.java
+++ b/common/src/com/android/tv/common/WeakHandler.java
@@ -20,7 +20,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.support.annotation.NonNull;
-
 import java.lang.ref.WeakReference;
 
 /**
@@ -37,7 +36,7 @@
     private final WeakReference<T> mRef;
 
     /**
-     * Constructs a new  handler with a weak reference to the given referent using the provided
+     * Constructs a new handler with a weak reference to the given referent using the provided
      * Looper instead of the default one.
      *
      * @param looper The looper, must not be null.
@@ -57,9 +56,7 @@
         mRef = new WeakReference<>(ref);
     }
 
-    /**
-     * Calls {@link #handleMessage(Message, Object)} if the WeakReference is not cleared.
-     */
+    /** Calls {@link #handleMessage(Message, Object)} if the WeakReference is not cleared. */
     @Override
     public final void handleMessage(Message msg) {
         T referent = mRef.get();
@@ -74,7 +71,7 @@
      *
      * <p>If the WeakReference is cleared this method will no longer be called.
      *
-     * @param msg      the message to handle
+     * @param msg the message to handle
      * @param referent the referent. Guaranteed to be non null.
      */
     protected abstract void handleMessage(Message msg, @NonNull T referent);
diff --git a/common/src/com/android/tv/common/actions/InputSetupActionUtils.java b/common/src/com/android/tv/common/actions/InputSetupActionUtils.java
new file mode 100644
index 0000000..7ba799e
--- /dev/null
+++ b/common/src/com/android/tv/common/actions/InputSetupActionUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2018 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.common.actions;
+
+import android.content.Intent;
+import android.media.tv.TvInputInfo;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+/** Constants and static utilities for the Input Setup Action. */
+public class InputSetupActionUtils {
+
+    /**
+     * An intent action to launch setup activity of a TV input.
+     *
+     * <p>The intent should include TV input ID in the value of {@link #EXTRA_INPUT_ID}. Optionally,
+     * given the value of {@link #EXTRA_ACTIVITY_AFTER_COMPLETION}, the activity will be launched
+     * after the setup activity successfully finishes.
+     */
+    public static final String INTENT_ACTION_INPUT_SETUP =
+            "com.android.tv.action.LAUNCH_INPUT_SETUP";
+    /**
+     * A constant of the key to indicate a TV input ID for the intent action {@link
+     * #INTENT_ACTION_INPUT_SETUP}.
+     *
+     * <p>Value type: String
+     */
+    public static final String EXTRA_INPUT_ID = TvInputInfo.EXTRA_INPUT_ID;
+    /**
+     * A constant of the key for intent to launch actual TV input setup activity used with {@link
+     * #INTENT_ACTION_INPUT_SETUP}.
+     *
+     * <p>Value type: Intent (Parcelable)
+     */
+    public static final String EXTRA_SETUP_INTENT = "com.android.tv.extra.SETUP_INTENT";
+    /**
+     * A constant of the key to indicate an Activity launch intent for the intent action {@link
+     * #INTENT_ACTION_INPUT_SETUP}.
+     *
+     * <p>Value type: Intent (Parcelable)
+     */
+    public static final String EXTRA_ACTIVITY_AFTER_COMPLETION =
+            "com.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION";
+    /**
+     * An intent action to launch setup activity of a TV input.
+     *
+     * <p>The intent should include TV input ID in the value of {@link #EXTRA_INPUT_ID}. Optionally,
+     * given the value of {@link #EXTRA_GOOGLE_ACTIVITY_AFTER_COMPLETION}, the activity will be
+     * launched after the setup activity successfully finishes.
+     *
+     * <p>Value type: Intent (Parcelable)
+     *
+     * @deprecated Use {@link #INTENT_ACTION_INPUT_SETUP} instead
+     */
+    @Deprecated
+    private static final String INTENT_GOOGLE_ACTION_INPUT_SETUP =
+            "com.google.android.tv.action.LAUNCH_INPUT_SETUP";
+    /**
+     * A Google specific constant of the key for intent to launch actual TV input setup activity
+     * used with {@link #INTENT_ACTION_INPUT_SETUP}.
+     *
+     * <p>Value type: Intent (Parcelable)
+     *
+     * @deprecated Use {@link #EXTRA_SETUP_INTENT} instead
+     */
+    @Deprecated
+    private static final String EXTRA_GOOGLE_SETUP_INTENT =
+            "com.google.android.tv.extra.SETUP_INTENT";
+    /**
+     * A Google specific constant of the key to indicate an Activity launch intent for the intent
+     * action {@link #INTENT_ACTION_INPUT_SETUP}.
+     *
+     * <p>Value type: Intent (Parcelable)
+     *
+     * @deprecated Use {@link #EXTRA_ACTIVITY_AFTER_COMPLETION} instead
+     */
+    @Deprecated
+    private static final String EXTRA_GOOGLE_ACTIVITY_AFTER_COMPLETION =
+            "com.google.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION";
+
+    public static void removeSetupIntent(Bundle extras) {
+        extras.remove(EXTRA_SETUP_INTENT);
+        extras.remove(EXTRA_GOOGLE_SETUP_INTENT);
+    }
+
+    @Nullable
+    public static Intent getExtraSetupIntent(Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (extras == null) {
+            return null;
+        }
+        Intent setupIntent = extras.getParcelable(EXTRA_SETUP_INTENT);
+        return setupIntent != null ? setupIntent : extras.getParcelable(EXTRA_GOOGLE_SETUP_INTENT);
+    }
+
+    @Nullable
+    public static Intent getExtraActivityAfter(Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (extras == null) {
+            return null;
+        }
+        Intent setupIntent = extras.getParcelable(EXTRA_ACTIVITY_AFTER_COMPLETION);
+        return setupIntent != null
+                ? setupIntent
+                : extras.getParcelable(EXTRA_GOOGLE_ACTIVITY_AFTER_COMPLETION);
+    }
+
+    public static boolean hasInputSetupAction(Intent intent) {
+        String action = intent.getAction();
+        return INTENT_ACTION_INPUT_SETUP.equals(action)
+                || INTENT_GOOGLE_ACTION_INPUT_SETUP.equals(action);
+    }
+}
diff --git a/common/src/com/android/tv/common/annotation/UsedByNative.java b/common/src/com/android/tv/common/annotation/UsedByNative.java
new file mode 100644
index 0000000..dc829e0
--- /dev/null
+++ b/common/src/com/android/tv/common/annotation/UsedByNative.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used for marking methods and fields that are called from native
+ * code. Useful for keeping components that would otherwise be removed by
+ * Proguard. Use the value parameter to mention a file that calls this method.
+ *
+ * Note that adding this annotation to a method is not enough to guarantee that
+ * it is kept - either its class must be referenced elsewhere in the program, or
+ * the class must be annotated with this as well.
+ *
+ * Usage example:<br />
+ *  <pre>{@code
+ *  @UsedByNative("NativeCrashHandler.cpp")
+ * public static void reportCrash(int signal, int code, int address) {
+ * ...
+ * }
+ * </pre>
+ *
+ */
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.CONSTRUCTOR})
+public @interface UsedByNative {
+    String value();
+}
diff --git a/common/src/com/android/tv/common/annotation/UsedByReflection.java b/common/src/com/android/tv/common/annotation/UsedByReflection.java
deleted file mode 100644
index 5a4517f..0000000
--- a/common/src/com/android/tv/common/annotation/UsedByReflection.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2015 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.common.annotation;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import java.lang.annotation.Retention;
-
-/**
- * Denotes that the method or field is used by reflection even though it is not ever called
- * directly.
- */
-@Retention(SOURCE)
-public @interface UsedByReflection {
-}
diff --git a/src/com/android/tv/util/NamedThreadFactory.java b/common/src/com/android/tv/common/concurrent/NamedThreadFactory.java
similarity index 87%
rename from src/com/android/tv/util/NamedThreadFactory.java
rename to common/src/com/android/tv/common/concurrent/NamedThreadFactory.java
index fcdde95..4c8371f 100644
--- a/src/com/android/tv/util/NamedThreadFactory.java
+++ b/common/src/com/android/tv/common/concurrent/NamedThreadFactory.java
@@ -14,25 +14,22 @@
  * limitations under the License
  */
 
-package com.android.tv.util;
+package com.android.tv.common.concurrent;
 
 import android.support.annotation.NonNull;
-
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
 
-/**
- * A thread factory that creates threads with a suffix.
- */
+/** A thread factory that creates threads with named <code>prefix-##</code>. */
 public class NamedThreadFactory implements ThreadFactory {
     private final AtomicInteger mCount = new AtomicInteger(0);
     private final ThreadFactory mDefaultThreadFactory;
     private final String mPrefix;
 
-    public NamedThreadFactory(final String baseName) {
+    public NamedThreadFactory(final String prefix) {
         mDefaultThreadFactory = Executors.defaultThreadFactory();
-        mPrefix = baseName + "-";
+        mPrefix = prefix + "-";
     }
 
     @Override
diff --git a/src/com/android/tv/config/DefaultConfigManager.java b/common/src/com/android/tv/common/config/DefaultConfigManager.java
similarity index 86%
rename from src/com/android/tv/config/DefaultConfigManager.java
rename to common/src/com/android/tv/common/config/DefaultConfigManager.java
index bbabc6d..ae24085 100644
--- a/src/com/android/tv/config/DefaultConfigManager.java
+++ b/common/src/com/android/tv/common/config/DefaultConfigManager.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.tv.config;
+package com.android.tv.common.config;
 
 import android.content.Context;
+import com.android.tv.common.config.api.RemoteConfig;
 
-/**
- * Stub Remote Config.
- */
+/** Stub Remote Config. */
 public class DefaultConfigManager {
     public static final long DEFAULT_LONG_VALUE = 0;
+
     public static DefaultConfigManager createInstance(Context context) {
         return new DefaultConfigManager();
     }
@@ -35,9 +35,7 @@
 
     private static class StubRemoteConfig implements RemoteConfig {
         @Override
-        public void fetch(OnRemoteConfigUpdatedListener listener) {
-
-        }
+        public void fetch(OnRemoteConfigUpdatedListener listener) {}
 
         @Override
         public String getString(String key) {
@@ -53,9 +51,10 @@
         public long getLong(String key) {
             return DEFAULT_LONG_VALUE;
         }
+
+        @Override
+        public long getLong(String key, long defaultValue) {
+            return defaultValue;
+        }
     }
 }
-
-
-
-
diff --git a/src/com/android/tv/config/RemoteConfigFeature.java b/common/src/com/android/tv/common/config/RemoteConfigFeature.java
similarity index 80%
rename from src/com/android/tv/config/RemoteConfigFeature.java
rename to common/src/com/android/tv/common/config/RemoteConfigFeature.java
index 502e6a9..2ea381f 100644
--- a/src/com/android/tv/config/RemoteConfigFeature.java
+++ b/common/src/com/android/tv/common/config/RemoteConfigFeature.java
@@ -14,15 +14,14 @@
  * limitations under the License
  */
 
-package com.android.tv.config;
+package com.android.tv.common.config;
 
 import android.content.Context;
-
-import com.android.tv.TvApplication;
+import com.android.tv.common.BaseApplication;
 import com.android.tv.common.feature.Feature;
 
 /**
- * A {@link Feature} controlled by a {@link RemoteConfig} boolean.
+ * A {@link Feature} controlled by a {@link com.android.tv.common.config.api.RemoteConfig} boolean.
  */
 public class RemoteConfigFeature implements Feature {
     private final String mKey;
@@ -38,6 +37,6 @@
 
     @Override
     public boolean isEnabled(Context context) {
-        return TvApplication.getSingletons(context).getRemoteConfig().getBoolean(mKey);
+        return BaseApplication.getSingletons(context).getRemoteConfig().getBoolean(mKey);
     }
 }
diff --git a/common/src/com/android/tv/common/config/api/RemoteConfig.java b/common/src/com/android/tv/common/config/api/RemoteConfig.java
new file mode 100644
index 0000000..74597f9
--- /dev/null
+++ b/common/src/com/android/tv/common/config/api/RemoteConfig.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 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.common.config.api;
+
+/**
+ * Manages Live TV Configuration, allowing remote updates.
+ *
+ * <p>This is a thin wrapper around <a
+ * href="https://firebase.google.com/docs/remote-config/"></a>Firebase Remote Config</a>
+ */
+public interface RemoteConfig {
+
+    /** Used to inject a remote config */
+    interface HasRemoteConfig {
+        RemoteConfig getRemoteConfig();
+    }
+
+    /** Notified on successful completion of a {@link #fetch)} */
+    interface OnRemoteConfigUpdatedListener {
+        void onRemoteConfigUpdated();
+    }
+
+    /** Starts a fetch and notifies {@code listener} on successful completion. */
+    void fetch(OnRemoteConfigUpdatedListener listener);
+
+    /** Gets value as a string corresponding to the specified key. */
+    String getString(String key);
+
+    /** Gets value as a boolean corresponding to the specified key. */
+    boolean getBoolean(String key);
+
+    /** Gets value as a long corresponding to the specified key. */
+    long getLong(String key);
+
+    /**
+     * Gets value as a long corresponding to the specified key. Returns the defaultValue if no value
+     * is found.
+     */
+    long getLong(String key, long defaultValue);
+}
diff --git a/common/src/com/android/tv/common/config/api/RemoteConfigValue.java b/common/src/com/android/tv/common/config/api/RemoteConfigValue.java
new file mode 100644
index 0000000..6da89fb
--- /dev/null
+++ b/common/src/com/android/tv/common/config/api/RemoteConfigValue.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 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.common.config.api;
+
+/** Wrapper for a RemoteConfig key and default value. */
+public abstract class RemoteConfigValue<T> {
+    private final T defaultValue;
+    private final String key;
+
+    private RemoteConfigValue(String key, T defaultValue) {
+        this.defaultValue = defaultValue;
+        this.key = key;
+    }
+
+    /** Create with the given key and default value */
+    public static RemoteConfigValue<Long> create(String key, long defaultValue) {
+        return new RemoteConfigValue<Long>(key, defaultValue) {
+            @Override
+            public Long get(RemoteConfig remoteConfig) {
+                return remoteConfig.getLong(key, defaultValue);
+            }
+        };
+    }
+
+    public abstract T get(RemoteConfig remoteConfig);
+
+    public final T getDefaultValue() {
+        return defaultValue;
+    }
+
+    @Override
+    public final String toString() {
+        return "RemoteConfigValue(key=" + key + ", defalutValue=" + defaultValue + "]";
+    }
+}
diff --git a/src/com/android/tv/customization/CustomAction.java b/common/src/com/android/tv/common/customization/CustomAction.java
similarity index 77%
rename from src/com/android/tv/customization/CustomAction.java
rename to common/src/com/android/tv/common/customization/CustomAction.java
index b8f4695..ee18cb3 100644
--- a/src/com/android/tv/customization/CustomAction.java
+++ b/common/src/com/android/tv/common/customization/CustomAction.java
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.tv.customization;
+package com.android.tv.common.customization;
 
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 
-/**
- * Describes a custom option defined in customization package.
- * This will be added to main menu.
- */
+/** Describes a custom option defined in customization package. This will be added to main menu. */
 public class CustomAction implements Comparable<CustomAction> {
     private static final int POSITION_THRESHOLD = 100;
 
@@ -40,9 +37,9 @@
     }
 
     /**
-     * Returns if this option comes before the existing items.
-     * Note that custom options can only be placed at the front or back.
-     * (i.e. cannot be added in the middle of existing options.)
+     * Returns if this option comes before the existing items. Note that custom options can only be
+     * placed at the front or back. (i.e. cannot be added in the middle of existing options.)
+     *
      * @return {@code true} if it goes to the beginning. {@code false} if it goes to the end.
      */
     public boolean isFront() {
@@ -54,23 +51,17 @@
         return mPositionPriority - another.mPositionPriority;
     }
 
-    /**
-     * Returns title.
-     */
+    /** Returns title. */
     public String getTitle() {
         return mTitle;
     }
 
-    /**
-     * Returns icon drawable.
-     */
+    /** Returns icon drawable. */
     public Drawable getIconDrawable() {
         return mIconDrawable;
     }
 
-    /**
-     * Returns intent to launch when this option is clicked.
-     */
+    /** Returns intent to launch when this option is clicked. */
     public Intent getIntent() {
         return mIntent;
     }
diff --git a/src/com/android/tv/customization/TvCustomizationManager.java b/common/src/com/android/tv/common/customization/CustomizationManager.java
similarity index 73%
rename from src/com/android/tv/customization/TvCustomizationManager.java
rename to common/src/com/android/tv/common/customization/CustomizationManager.java
index ed6b98c..09ecaef 100644
--- a/src/com/android/tv/customization/TvCustomizationManager.java
+++ b/common/src/com/android/tv/common/customization/CustomizationManager.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.tv.customization;
+package com.android.tv.common.customization;
 
 import android.content.Context;
 import android.content.Intent;
@@ -27,7 +27,7 @@
 import android.support.annotation.IntDef;
 import android.text.TextUtils;
 import android.util.Log;
-
+import com.android.tv.common.CommonConstants;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -36,38 +36,36 @@
 import java.util.List;
 import java.util.Map;
 
-public class TvCustomizationManager {
-    private static final String TAG = "TvCustomizationManager";
+public class CustomizationManager {
+    private static final String TAG = "CustomizationManager";
     private static final boolean DEBUG = false;
 
     private static final String[] CUSTOMIZE_PERMISSIONS = {
-            "com.android.tv.permission.CUSTOMIZE_TV_APP"
+        CommonConstants.BASE_PACKAGE + ".permission.CUSTOMIZE_TV_APP"
     };
 
     private static final String CATEGORY_TV_CUSTOMIZATION =
-            "com.android.tv.category";
+            CommonConstants.BASE_PACKAGE + ".category";
 
-    /**
-     * Row IDs to share customized actions.
-     * Only rows listed below can have customized action.
-     */
+    /** Row IDs to share customized actions. Only rows listed below can have customized action. */
     public static final String ID_OPTIONS_ROW = "options_row";
+
     public static final String ID_PARTNER_ROW = "partner_row";
 
     @IntDef({TRICKPLAY_MODE_ENABLED, TRICKPLAY_MODE_DISABLED, TRICKPLAY_MODE_USE_EXTERNAL_STORAGE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface TRICKPLAY_MODE {}
+
     public static final int TRICKPLAY_MODE_ENABLED = 0;
     public static final int TRICKPLAY_MODE_DISABLED = 1;
     public static final int TRICKPLAY_MODE_USE_EXTERNAL_STORAGE = 2;
 
     private static final String[] TRICKPLAY_MODE_STRINGS = {
-        "enabled",
-        "disabled",
-        "use_external_storage_only"
+        "enabled", "disabled", "use_external_storage_only"
     };
 
     private static final HashMap<String, String> INTENT_CATEGORY_TO_ROW_ID;
+
     static {
         INTENT_CATEGORY_TO_ROW_ID = new HashMap<>();
         INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".OPTIONS_ROW", ID_OPTIONS_ROW);
@@ -92,7 +90,7 @@
     private String mPartnerRowTitle;
     private final Map<String, List<CustomAction>> mRowIdToCustomActionsMap = new HashMap<>();
 
-    public TvCustomizationManager(Context context) {
+    public CustomizationManager(Context context) {
         mContext = context;
         mInitialized = false;
     }
@@ -108,10 +106,14 @@
                 sHasLinuxDvbBuiltInTuner = false;
             } else {
                 try {
-                    Resources res = context.getPackageManager()
-                            .getResourcesForApplication(sCustomizationPackage);
-                    int resId = res.getIdentifier(RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER,
-                            RES_TYPE_BOOLEAN, sCustomizationPackage);
+                    Resources res =
+                            context.getPackageManager()
+                                    .getResourcesForApplication(sCustomizationPackage);
+                    int resId =
+                            res.getIdentifier(
+                                    RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER,
+                                    RES_TYPE_BOOLEAN,
+                                    sCustomizationPackage);
                     sHasLinuxDvbBuiltInTuner = resId != 0 && res.getBoolean(resId);
                 } catch (NameNotFoundException e) {
                     sHasLinuxDvbBuiltInTuner = false;
@@ -128,10 +130,12 @@
             } else {
                 try {
                     String customization = null;
-                    Resources res = context.getPackageManager()
-                            .getResourcesForApplication(sCustomizationPackage);
-                    int resId = res.getIdentifier(RES_ID_TRICKPLAY_MODE,
-                            RES_TYPE_STRING, sCustomizationPackage);
+                    Resources res =
+                            context.getPackageManager()
+                                    .getResourcesForApplication(sCustomizationPackage);
+                    int resId =
+                            res.getIdentifier(
+                                    RES_ID_TRICKPLAY_MODE, RES_TYPE_STRING, sCustomizationPackage);
                     customization = resId == 0 ? null : res.getString(resId);
                     sTrickplayMode = TRICKPLAY_MODE_ENABLED;
                     if (customization != null) {
@@ -152,17 +156,15 @@
 
     private static String getCustomizationPackageName(Context context) {
         if (sCustomizationPackage == null) {
-            List<PackageInfo> packageInfos = context.getPackageManager()
-                    .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0);
+            List<PackageInfo> packageInfos =
+                    context.getPackageManager()
+                            .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0);
             sCustomizationPackage = packageInfos.size() == 0 ? "" : packageInfos.get(0).packageName;
         }
         return sCustomizationPackage;
     }
 
-    /**
-     * Initialize TV customization options.
-     * Run this API only on the main thread.
-     */
+    /** Initialize TV customization options. Run this API only on the main thread. */
     public void initialize() {
         if (mInitialized) {
             return;
@@ -181,14 +183,21 @@
             Intent customOptionIntent = new Intent(Intent.ACTION_MAIN);
             customOptionIntent.addCategory(intentCategory);
 
-            List<ResolveInfo> activities = pm.queryIntentActivities(customOptionIntent,
-                    PackageManager.GET_RECEIVERS | PackageManager.GET_RESOLVED_FILTER
-                            | PackageManager.GET_META_DATA);
+            List<ResolveInfo> activities =
+                    pm.queryIntentActivities(
+                            customOptionIntent,
+                            PackageManager.GET_RECEIVERS
+                                    | PackageManager.GET_RESOLVED_FILTER
+                                    | PackageManager.GET_META_DATA);
             for (ResolveInfo info : activities) {
                 String packageName = info.activityInfo.packageName;
                 if (!TextUtils.equals(packageName, sCustomizationPackage)) {
-                    Log.w(TAG, "A customization package " + sCustomizationPackage
-                            + " already exist. Ignoring " + packageName);
+                    Log.w(
+                            TAG,
+                            "A customization package "
+                                    + sCustomizationPackage
+                                    + " already exist. Ignoring "
+                                    + packageName);
                     continue;
                 }
 
@@ -217,8 +226,14 @@
             Log.d(TAG, "Dumping custom actions");
             for (String id : mRowIdToCustomActionsMap.keySet()) {
                 for (CustomAction action : mRowIdToCustomActionsMap.get(id)) {
-                    Log.d(TAG, "Custom row rowId=" + id + " title=" + action.getTitle()
-                        + " class=" + action.getIntent());
+                    Log.d(
+                            TAG,
+                            "Custom row rowId="
+                                    + id
+                                    + " title="
+                                    + action.getTitle()
+                                    + " class="
+                                    + action.getIntent());
                 }
             }
             Log.d(TAG, "Dumping custom actions - end of dump");
@@ -228,7 +243,7 @@
     /**
      * Returns custom actions for given row id.
      *
-     * Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW.
+     * <p>Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW.
      */
     public List<CustomAction> getCustomActions(String rowId) {
         return mRowIdToCustomActionsMap.get(rowId);
@@ -238,23 +253,20 @@
         mPartnerRowTitle = null;
         Resources res;
         try {
-            res = mContext.getPackageManager()
-                    .getResourcesForApplication(sCustomizationPackage);
+            res = mContext.getPackageManager().getResourcesForApplication(sCustomizationPackage);
         } catch (NameNotFoundException e) {
             Log.w(TAG, "Could not get resources for package " + sCustomizationPackage);
             return;
         }
-        int resId = res.getIdentifier(
-                RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage);
+        int resId =
+                res.getIdentifier(RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage);
         if (resId != 0) {
             mPartnerRowTitle = res.getString(resId);
         }
         if (DEBUG) Log.d(TAG, "Partner row title [" + mPartnerRowTitle + "]");
     }
 
-    /**
-     * Returns partner row title.
-     */
+    /** Returns partner row title. */
     public String getPartnerRowTitle() {
         return mPartnerRowTitle;
     }
diff --git a/src/com/android/tv/experiments/ExperimentFlag.java b/common/src/com/android/tv/common/experiments/ExperimentFlag.java
similarity index 91%
rename from src/com/android/tv/experiments/ExperimentFlag.java
rename to common/src/com/android/tv/common/experiments/ExperimentFlag.java
index c0cbd64..c9bacac 100644
--- a/src/com/android/tv/experiments/ExperimentFlag.java
+++ b/common/src/com/android/tv/common/experiments/ExperimentFlag.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.tv.experiments;
+package com.android.tv.common.experiments;
 
 import android.support.annotation.VisibleForTesting;
+import com.android.tv.common.BuildConfig;
 
-/**
- * Experiments return values based on user, device and other criteria.
- */
+
+/** Experiments return values based on user, device and other criteria. */
 public final class ExperimentFlag<T> {
 
     private static boolean sAllowOverrides = false;
@@ -64,7 +64,4 @@
     public void resetOverride() {
         mOverridden = false;
     }
-
-
-
 }
diff --git a/common/src/com/android/tv/common/experiments/ExperimentLoader.java b/common/src/com/android/tv/common/experiments/ExperimentLoader.java
new file mode 100644
index 0000000..5f012e1
--- /dev/null
+++ b/common/src/com/android/tv/common/experiments/ExperimentLoader.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 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.common.experiments;
+
+import android.content.Context;
+
+/** Used to sync {@link ExperimentFlag}s. */
+public class ExperimentLoader {
+
+    /** Starts a background task to update {@link ExperimentFlag}s */
+    public void asyncRefreshExperiments(Context context) {
+        // Override for your experiment system
+    }
+}
diff --git a/common/src/com/android/tv/common/experiments/Experiments.java b/common/src/com/android/tv/common/experiments/Experiments.java
new file mode 100644
index 0000000..96b15e5
--- /dev/null
+++ b/common/src/com/android/tv/common/experiments/Experiments.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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.common.experiments;
+
+import static com.android.tv.common.experiments.ExperimentFlag.createFlag;
+
+import com.android.tv.common.BuildConfig;
+
+/**
+ * Set of experiments visible in AOSP.
+ *
+ * <p>This file is maintained by hand.
+ */
+public final class Experiments {
+    public static final ExperimentFlag<Boolean> CLOUD_EPG =
+            ExperimentFlag.createFlag(
+                    true);
+
+    public static final ExperimentFlag<Boolean> ENABLE_UNRATED_CONTENT_SETTINGS =
+            ExperimentFlag.createFlag(
+                    false);
+
+    /** Turn analytics on or off based on the System Checkbox for logging. */
+    public static final ExperimentFlag<Boolean> ENABLE_ANALYTICS_VIA_CHECKBOX =
+            createFlag(
+                    false);
+
+    /**
+     * Allow developer features such as the dev menu and other aids.
+     *
+     * <p>These features are available to select users(aka fishfooders) on production builds.
+     */
+    public static final ExperimentFlag<Boolean> ENABLE_DEVELOPER_FEATURES =
+            ExperimentFlag.createFlag(
+                    BuildConfig.ENG);
+
+    /**
+     * Allow QA features.
+     *
+     * <p>These features must be carefully limited, keeping QA differences to a minimum.
+     *
+     * <p>These features are available to select users(aka QA) on production builds.
+     */
+    public static final ExperimentFlag<Boolean> ENABLE_QA_FEATURES =
+            ExperimentFlag.createFlag(
+                    false);
+
+    private Experiments() {}
+}
diff --git a/common/src/com/android/tv/common/feature/CommonFeatures.java b/common/src/com/android/tv/common/feature/CommonFeatures.java
index 62c88ea..1fceabb 100644
--- a/common/src/com/android/tv/common/feature/CommonFeatures.java
+++ b/common/src/com/android/tv/common/feature/CommonFeatures.java
@@ -19,36 +19,81 @@
 import static com.android.tv.common.feature.FeatureUtils.AND;
 import static com.android.tv.common.feature.TestableFeature.createTestableFeature;
 
+import android.content.Context;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.tv.common.config.api.RemoteConfig.HasRemoteConfig;
+import com.android.tv.common.experiments.Experiments;
+
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.LocationUtils;
+
 /**
  * List of {@link Feature} that affect more than just the Live TV app.
  *
  * <p>Remove the {@code Feature} once it is launched.
  */
 public class CommonFeatures {
+    private static final String TAG = "CommonFeatures";
+    private static final boolean DEBUG = false;
+
     /**
      * DVR
      *
      * <p>See <a href="https://goto.google.com/atv-dvr-onepager">go/atv-dvr-onepager</a>
      *
-     * DVR API is introduced in N, it only works when app runs as a system app.
+     * <p>DVR API is introduced in N, it only works when app runs as a system app.
      */
-    public static final TestableFeature DVR = createTestableFeature(
-            AND(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE));
+    public static final TestableFeature DVR =
+            createTestableFeature(AND(Sdk.AT_LEAST_N, SystemAppFeature.SYSTEM_APP_FEATURE));
 
     /**
      * ENABLE_RECORDING_REGARDLESS_OF_STORAGE_STATUS
      *
-     * Enables dvr recording regardless of storage status.
+     * <p>Enables dvr recording regardless of storage status.
      */
     public static final Feature FORCE_RECORDING_UNTIL_NO_SPACE =
-            new PropertyFeature("force_recording_until_no_space", false);
+            PropertyFeature.create("force_recording_until_no_space", false);
 
-    /**
-     * USE_SW_CODEC_FOR_SD
-     *
-     * Prefer software based codec for SD channels.
-     */
-    public static final Feature USE_SW_CODEC_FOR_SD =
-            new PropertyFeature("use_sw_codec_for_sd", false
-            );
+    public static final Feature TUNER =
+            new Feature() {
+                @Override
+                public boolean isEnabled(Context context) {
+
+                    if (CommonUtils.isDeveloper()) {
+                        // we enable tuner for developers to test tuner in any platform.
+                        return true;
+                    }
+
+                    // This is special handling just for USB Tuner.
+                    // It does not require any N API's but relies on a improvements in N for AC3
+                    // support
+                    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+                }
+            };
+
+    /** Show postal code fragment before channel scan. */
+    public static final Feature ENABLE_CLOUD_EPG_REGION =
+            new Feature() {
+                private final String[] supportedRegions = {
+                };
+
+
+                @Override
+                public boolean isEnabled(Context context) {
+                    if (!Experiments.CLOUD_EPG.get()) {
+                        if (DEBUG) Log.d(TAG, "Experiments.CLOUD_EPG is false");
+                        return false;
+                    }
+                    String country = LocationUtils.getCurrentCountry(context);
+                    for (int i = 0; i < supportedRegions.length; i++) {
+                        if (supportedRegions[i].equalsIgnoreCase(country)) {
+                            return true;
+                        }
+                    }
+                    if (DEBUG) Log.d(TAG, "EPG flag false after country check");
+                    return false;
+                }
+            };
 }
diff --git a/common/src/com/android/tv/common/feature/EngOnlyFeature.java b/common/src/com/android/tv/common/feature/EngOnlyFeature.java
index 9fc39d9..5feb548 100644
--- a/common/src/com/android/tv/common/feature/EngOnlyFeature.java
+++ b/common/src/com/android/tv/common/feature/EngOnlyFeature.java
@@ -17,16 +17,13 @@
 package com.android.tv.common.feature;
 
 import android.content.Context;
-
 import com.android.tv.common.BuildConfig;
 
-/**
- * A feature that is only available on {@link BuildConfig#ENG} builds.
- */
+/** A feature that is only available on {@link BuildConfig#ENG} builds. */
 public final class EngOnlyFeature implements Feature {
     public static final Feature ENG_ONLY_FEATURE = new EngOnlyFeature();
 
-    private EngOnlyFeature() { }
+    private EngOnlyFeature() {}
 
     @Override
     public boolean isEnabled(Context context) {
diff --git a/common/src/com/android/tv/common/feature/ExperimentFeature.java b/common/src/com/android/tv/common/feature/ExperimentFeature.java
new file mode 100644
index 0000000..820eda4
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/ExperimentFeature.java
@@ -0,0 +1,44 @@
+/*
+ * 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.common.feature;
+
+import android.content.Context;
+import com.android.tv.common.experiments.ExperimentFlag;
+
+/** A {@link Feature} base on an {@link ExperimentFlag}. */
+public final class ExperimentFeature implements Feature {
+
+    public static Feature from(ExperimentFlag<Boolean> flag) {
+        return new ExperimentFeature(flag);
+    }
+
+    private final ExperimentFlag<Boolean> mFlag;
+
+    private ExperimentFeature(ExperimentFlag<Boolean> flag) {
+        mFlag = flag;
+    }
+
+    @Override
+    public boolean isEnabled(Context context) {
+        return mFlag.get();
+    }
+
+    @Override
+    public String toString() {
+        return "ExperimentFeature for " + mFlag;
+    }
+}
diff --git a/common/src/com/android/tv/common/feature/Feature.java b/common/src/com/android/tv/common/feature/Feature.java
index b3a8336..fd5625e 100644
--- a/common/src/com/android/tv/common/feature/Feature.java
+++ b/common/src/com/android/tv/common/feature/Feature.java
@@ -23,16 +23,15 @@
  * launched.
  *
  * <p>Expected usage is:
+ *
  * <pre>{@code
- *     if (MY_FEATURE.isEnabled(context) {
- *         showNewCoolUi();
- *     } else{
- *         showOldBoringUi();
- *     }
+ * if (MY_FEATURE.isEnabled(context) {
+ *     showNewCoolUi();
+ * } else{
+ *     showOldBoringUi();
+ * }
  * }</pre>
  */
 public interface Feature {
     boolean isEnabled(Context context);
-
-
 }
diff --git a/common/src/com/android/tv/common/feature/FeatureUtils.java b/common/src/com/android/tv/common/feature/FeatureUtils.java
index f60b204..8650d15 100644
--- a/common/src/com/android/tv/common/feature/FeatureUtils.java
+++ b/common/src/com/android/tv/common/feature/FeatureUtils.java
@@ -17,12 +17,10 @@
 package com.android.tv.common.feature;
 
 import android.content.Context;
-
+import com.android.tv.common.util.CommonUtils;
 import java.util.Arrays;
 
-/**
- * Static utilities for features.
- */
+/** Static utilities for features. */
 public class FeatureUtils {
 
     /**
@@ -47,7 +45,6 @@
                 return "or(" + Arrays.asList(features) + ")";
             }
         };
-
     }
 
     /**
@@ -74,36 +71,47 @@
         };
     }
 
-    /**
-     * A feature that is always enabled.
-     */
-    public static final Feature ON = new Feature() {
-        @Override
-        public boolean isEnabled(Context context) {
-            return true;
-        }
+    /** A feature that is always enabled. */
+    public static final Feature ON =
+            new Feature() {
+                @Override
+                public boolean isEnabled(Context context) {
+                    return true;
+                }
 
-        @Override
-        public String toString() {
-            return "on";
-        }
-    };
+                @Override
+                public String toString() {
+                    return "on";
+                }
+            };
 
-    /**
-     * A feature that is always disabled.
-     */
-    public static final Feature OFF = new Feature() {
-        @Override
-        public boolean isEnabled(Context context) {
-            return false;
-        }
+    /** A feature that is always disabled. */
+    public static final Feature OFF =
+            new Feature() {
+                @Override
+                public boolean isEnabled(Context context) {
+                    return false;
+                }
 
-        @Override
-        public String toString() {
-            return "off";
-        }
-    };
+                @Override
+                public String toString() {
+                    return "off";
+                }
+            };
 
-    private FeatureUtils() {
-    }
+    /** True if running in robolectric. */
+    public static final Feature ROBOLECTRIC =
+            new Feature() {
+                @Override
+                public boolean isEnabled(Context context) {
+                    return CommonUtils.isRoboTest();
+                }
+
+                @Override
+                public String toString() {
+                    return "isRobolecteric";
+                }
+            };
+
+    private FeatureUtils() {}
 }
diff --git a/common/src/com/android/tv/common/feature/GServiceFeature.java b/common/src/com/android/tv/common/feature/GServiceFeature.java
index 9e6e11a..1d7d115 100644
--- a/common/src/com/android/tv/common/feature/GServiceFeature.java
+++ b/common/src/com/android/tv/common/feature/GServiceFeature.java
@@ -18,10 +18,7 @@
 
 import android.content.Context;
 
-
-/**
- * A feature controlled by a GServices flag.
- */
+/** A feature controlled by a GServices flag. */
 public class GServiceFeature implements Feature {
     private static final String LIVECHANNELS_PREFIX = "livechannels:";
     private final String mKey;
diff --git a/common/src/com/android/tv/common/feature/Model.java b/common/src/com/android/tv/common/feature/Model.java
new file mode 100644
index 0000000..7aa5148
--- /dev/null
+++ b/common/src/com/android/tv/common/feature/Model.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 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.common.feature;
+
+import android.content.Context;
+
+/** Holder for {@link android.os.Build#MODEL} features. */
+public interface Model {
+
+    ModelFeature NEXUS_PLAYER = new ModelFeature("Nexus Player");
+
+    /** True when the {@link android.os.Build#MODEL} equals the {@code model} given. */
+    public static final class ModelFeature implements Feature {
+        private final String mModel;
+
+        private ModelFeature(String model) {
+            mModel = model;
+        }
+
+        @Override
+        public boolean isEnabled(Context context) {
+            return isEnabled();
+        }
+
+        public boolean isEnabled() {
+            return android.os.Build.MODEL.equals(mModel);
+        }
+
+        @Override
+        public String toString() {
+            return "ModelFeature(" + mModel + ")=" + isEnabled();
+        }
+    }
+}
diff --git a/common/src/com/android/tv/common/feature/PackageVersionFeature.java b/common/src/com/android/tv/common/feature/PackageVersionFeature.java
index 7f615d3..f41a14a 100644
--- a/common/src/com/android/tv/common/feature/PackageVersionFeature.java
+++ b/common/src/com/android/tv/common/feature/PackageVersionFeature.java
@@ -49,7 +49,10 @@
 
     @Override
     public String toString() {
-        return "PackageVersionFeature[packageName=" + mPackageName + ",requiredVersion="
-                + mRequiredVersionCode + "]";
+        return "PackageVersionFeature[packageName="
+                + mPackageName
+                + ",requiredVersion="
+                + mRequiredVersionCode
+                + "]";
     }
 }
diff --git a/common/src/com/android/tv/common/feature/PropertyFeature.java b/common/src/com/android/tv/common/feature/PropertyFeature.java
index fdcffa0..0cf3631 100644
--- a/common/src/com/android/tv/common/feature/PropertyFeature.java
+++ b/common/src/com/android/tv/common/feature/PropertyFeature.java
@@ -17,7 +17,6 @@
 package com.android.tv.common.feature;
 
 import android.content.Context;
-
 import com.android.tv.common.BooleanSystemProperty;
 
 /**
@@ -26,21 +25,29 @@
  * <p>See {@link BooleanSystemProperty} for instructions on how to set using adb.
  */
 public final class PropertyFeature implements Feature {
+
+    public static PropertyFeature create(String key, boolean defaultValue) {
+        return new PropertyFeature(key, defaultValue);
+    }
+
     private final BooleanSystemProperty mProperty;
 
     /**
      * Create System Property backed feature.
      *
-     * @param key the system property key.  Length must be <= 31 characters.
+     * @param key the system property key. Length must be <= 31 characters.
      * @param defaultValue the value to return if the property is undefined or empty.
      */
-    public PropertyFeature(String key, boolean defaultValue) {
+    private PropertyFeature(String key, boolean defaultValue) {
         if (key.length() > 31) {
             // Since Features are initialized at startup and the keys are static go ahead and kill
             // the application.
             throw new IllegalArgumentException(
-                    "Property keys have a max length of 31 characters but '" + key + "' is " + key
-                            .length() + " characters.");
+                    "Property keys have a max length of 31 characters but '"
+                            + key
+                            + "' is "
+                            + key.length()
+                            + " characters.");
         }
         mProperty = new BooleanSystemProperty(key, defaultValue);
     }
diff --git a/common/src/com/android/tv/common/feature/Sdk.java b/common/src/com/android/tv/common/feature/Sdk.java
index 9f99a64..155b391 100644
--- a/common/src/com/android/tv/common/feature/Sdk.java
+++ b/common/src/com/android/tv/common/feature/Sdk.java
@@ -19,10 +19,8 @@
 import android.content.Context;
 import android.os.Build;
 
-/**
- *  Holder for SDK version features
- */
-public class Sdk {
+/** Holder for SDK version features */
+public final class Sdk {
     public static final Feature AT_LEAST_N =
             new Feature() {
                 @Override
@@ -31,5 +29,13 @@
                 }
             };
 
+    public static final Feature AT_LEAST_O =
+            new Feature() {
+                @Override
+                public boolean isEnabled(Context context) {
+                    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+                }
+            };
+
     private Sdk() {}
 }
diff --git a/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java b/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java
index 881f53d..ef2260d 100644
--- a/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java
+++ b/common/src/com/android/tv/common/feature/SharedPreferencesFeature.java
@@ -19,11 +19,9 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.util.Log;
-import com.android.tv.common.SharedPreferencesUtils;
+import com.android.tv.common.util.SharedPreferencesUtils;
 
-/**
- * Feature controlled by shared preferences.
- */
+/** Feature controlled by shared preferences. */
 public final class SharedPreferencesFeature implements Feature {
     private static final String TAG = "SharedPrefFeature";
     private static final boolean DEBUG = false;
@@ -53,8 +51,9 @@
             return false;
         }
         if (mSharedPreferences == null) {
-            mSharedPreferences = context.getSharedPreferences(
-                    SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
+            mSharedPreferences =
+                    context.getSharedPreferences(
+                            SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
             mEnabled = mSharedPreferences.getBoolean(mKey, mDefaultValue);
         }
         if (DEBUG) Log.d(TAG, mKey + " is " + mEnabled);
@@ -69,14 +68,14 @@
     public void setEnabled(Context context, boolean enable) {
         if (DEBUG) Log.d(TAG, mKey + " is set to " + enable);
         if (mSharedPreferences == null) {
-            mSharedPreferences = context.getSharedPreferences(
-                    SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
+            mSharedPreferences =
+                    context.getSharedPreferences(
+                            SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
             mEnabled = enable;
             mSharedPreferences.edit().putBoolean(mKey, enable).apply();
         } else if (mEnabled != enable) {
             mEnabled = enable;
             mSharedPreferences.edit().putBoolean(mKey, enable).apply();
         }
-
     }
 }
diff --git a/common/src/com/android/tv/common/feature/SystemAppFeature.java b/common/src/com/android/tv/common/feature/SystemAppFeature.java
index 79fd32f..1dd3b8d 100644
--- a/common/src/com/android/tv/common/feature/SystemAppFeature.java
+++ b/common/src/com/android/tv/common/feature/SystemAppFeature.java
@@ -19,13 +19,11 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 
-/**
- * A feature that is for system App.
- */
+/** A feature that is for system App. */
 public final class SystemAppFeature implements Feature {
     public static final Feature SYSTEM_APP_FEATURE = new SystemAppFeature();
 
-    private SystemAppFeature() { }
+    private SystemAppFeature() {}
 
     @Override
     public boolean isEnabled(Context context) {
diff --git a/common/src/com/android/tv/common/feature/TestableFeature.java b/common/src/com/android/tv/common/feature/TestableFeature.java
index d7e707a..1f18639 100644
--- a/common/src/com/android/tv/common/feature/TestableFeature.java
+++ b/common/src/com/android/tv/common/feature/TestableFeature.java
@@ -19,33 +19,28 @@
 import android.content.Context;
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
-
-import com.android.tv.common.TvCommonUtils;
+import com.android.tv.common.util.CommonUtils;
 
 /**
  * When run in a test harness this feature can be turned on or off, overriding the normal value.
  *
- * <p><b>Warning</b> making a feature testable will cause the code to stay in the APK and
- * could leak unreleased features.
+ * <p><b>Warning</b> making a feature testable will cause the code to stay in the APK and could leak
+ * unreleased features.
  */
 public class TestableFeature implements Feature {
-    private final static String TAG = "TestableFeature";
-    private final static String DETAIL_MESSAGE
-            = "TestableFeatures should only be changed in tests.";
+    private static final String TAG = "TestableFeature";
+    private static final String DETAIL_MESSAGE =
+            "TestableFeatures should only be changed in tests.";
 
     private final Feature mDelegate;
     private Boolean mTestValue = null;
 
-    /**
-     * Creates testable feature.
-     */
+    /** Creates testable feature. */
     public static TestableFeature createTestableFeature(Feature delegate) {
         return new TestableFeature(delegate);
     }
 
-    /**
-     * Creates testable feature with initial value.
-     */
+    /** Creates testable feature with initial value. */
     public static TestableFeature createTestableFeature(Feature delegate, Boolean initialValue) {
         return new TestableFeature(delegate, initialValue);
     }
@@ -61,9 +56,8 @@
 
     @VisibleForTesting
     public void enableForTest() {
-        if (!TvCommonUtils.isRunningInTest()) {
-            Log.e(TAG, "Not enabling for test:" + this,
-                    new IllegalStateException(DETAIL_MESSAGE));
+        if (!CommonUtils.isRunningInTest()) {
+            Log.e(TAG, "Not enabling for test:" + this, new IllegalStateException(DETAIL_MESSAGE));
         } else {
             mTestValue = true;
         }
@@ -71,8 +65,10 @@
 
     @VisibleForTesting
     public void disableForTests() {
-        if (!TvCommonUtils.isRunningInTest()) {
-            Log.e(TAG, "Not disabling for test: " + this,
+        if (!CommonUtils.isRunningInTest()) {
+            Log.e(
+                    TAG,
+                    "Not disabling for test: " + this,
                     new IllegalStateException(DETAIL_MESSAGE));
         } else {
             mTestValue = false;
@@ -81,7 +77,7 @@
 
     @VisibleForTesting
     public void resetForTests() {
-        if (!TvCommonUtils.isRunningInTest()) {
+        if (!CommonUtils.isRunningInTest()) {
             Log.e(TAG, "Not resetting feature: " + this, new IllegalStateException(DETAIL_MESSAGE));
         } else {
             mTestValue = null;
@@ -90,7 +86,7 @@
 
     @Override
     public boolean isEnabled(Context context) {
-        if (TvCommonUtils.isRunningInTest() && mTestValue != null) {
+        if (CommonUtils.isRunningInTest() && mTestValue != null) {
             return mTestValue;
         }
         return mDelegate.isEnabled(context);
@@ -99,7 +95,7 @@
     @Override
     public String toString() {
         String msg = mDelegate.toString();
-        if (TvCommonUtils.isRunningInTest()) {
+        if (CommonUtils.isRunningInTest()) {
             if (mTestValue == null) {
                 msg = "Testable Feature is unchanged: " + msg;
             } else {
diff --git a/common/src/com/android/tv/common/MemoryManageable.java b/common/src/com/android/tv/common/memory/MemoryManageable.java
similarity index 67%
rename from common/src/com/android/tv/common/MemoryManageable.java
rename to common/src/com/android/tv/common/memory/MemoryManageable.java
index 0cb3610..3e81fb5 100644
--- a/common/src/com/android/tv/common/MemoryManageable.java
+++ b/common/src/com/android/tv/common/memory/MemoryManageable.java
@@ -14,16 +14,13 @@
  * limitations under the License
  */
 
-package com.android.tv.common;
+package com.android.tv.common.memory;
 
 /**
- * Interface for the fine-grained memory management.
- * The class which wants to release memory based on the system constraints should inherit
- * this interface and implement {@link #performTrimMemory}.
+ * Interface for the fine-grained memory management. The class which wants to release memory based
+ * on the system constraints should inherit this interface and implement {@link #performTrimMemory}.
  */
 public interface MemoryManageable {
-    /**
-     * For more information, see {@link android.content.ComponentCallbacks2#onTrimMemory}.
-     */
+    /** For more information, see {@link android.content.ComponentCallbacks2#onTrimMemory}. */
     void performTrimMemory(int level);
 }
diff --git a/common/src/com/android/tv/common/recording/RecordingCapability.java b/common/src/com/android/tv/common/recording/RecordingCapability.java
index 266fd27..9fef527 100644
--- a/common/src/com/android/tv/common/recording/RecordingCapability.java
+++ b/common/src/com/android/tv/common/recording/RecordingCapability.java
@@ -18,16 +18,11 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-
 import java.util.Objects;
 
-/**
- * Static representation of the recording capability of a TvInputService.
- */
-public final class RecordingCapability implements Parcelable{
-    /**
-     * The inputId this capability represents.
-     */
+/** Static representation of the recording capability of a TvInputService. */
+public final class RecordingCapability implements Parcelable {
+    /** The inputId this capability represents. */
     public final String inputId;
 
     /**
@@ -40,8 +35,8 @@
     /**
      * The max number concurrent session that play a stream.
      *
-     *<p>This is often limited by the number of decoders available.
-     * The count includes both playing live TV and playing a recorded stream.
+     * <p>This is often limited by the number of decoders available. The count includes both playing
+     * live TV and playing a recorded stream.
      */
     public final int maxConcurrentPlayingSessions;
 
@@ -52,13 +47,14 @@
      */
     public final int maxConcurrentSessionsOfAllTypes;
 
-    /**
-     * True if a tuned session can support recording and playback from the same resource.
-     */
+    /** True if a tuned session can support recording and playback from the same resource. */
     public final boolean playbackWhileRecording;
 
-    private RecordingCapability(String inputId, int maxConcurrentTunedSessions,
-            int maxConcurrentPlayingSessions, int maxConcurrentSessionsOfAllTypes,
+    private RecordingCapability(
+            String inputId,
+            int maxConcurrentTunedSessions,
+            int maxConcurrentPlayingSessions,
+            int maxConcurrentSessionsOfAllTypes,
             boolean playbackWhileRecording) {
         this.inputId = inputId;
         this.maxConcurrentTunedSessions = maxConcurrentTunedSessions;
@@ -93,12 +89,12 @@
             return false;
         }
         RecordingCapability that = (RecordingCapability) o;
-        return Objects.equals(maxConcurrentTunedSessions, that.maxConcurrentTunedSessions) &&
-                Objects.equals(maxConcurrentPlayingSessions, that.maxConcurrentPlayingSessions) &&
-                Objects.equals(maxConcurrentSessionsOfAllTypes,
-                        that.maxConcurrentSessionsOfAllTypes) &&
-                Objects.equals(playbackWhileRecording, that.playbackWhileRecording) &&
-                Objects.equals(inputId, that.inputId);
+        return Objects.equals(maxConcurrentTunedSessions, that.maxConcurrentTunedSessions)
+                && Objects.equals(maxConcurrentPlayingSessions, that.maxConcurrentPlayingSessions)
+                && Objects.equals(
+                        maxConcurrentSessionsOfAllTypes, that.maxConcurrentSessionsOfAllTypes)
+                && Objects.equals(playbackWhileRecording, that.playbackWhileRecording)
+                && Objects.equals(inputId, that.inputId);
     }
 
     @Override
@@ -108,13 +104,19 @@
 
     @Override
     public String toString() {
-        return "RecordingCapability{" +
-                "inputId='" + inputId + '\'' +
-                ", maxConcurrentTunedSessions=" + maxConcurrentTunedSessions +
-                ", maxConcurrentPlayingSessions=" + maxConcurrentPlayingSessions +
-                ", maxConcurrentSessionsOfAllTypes=" + maxConcurrentSessionsOfAllTypes +
-                ", playbackWhileRecording=" + playbackWhileRecording +
-                '}';
+        return "RecordingCapability{"
+                + "inputId='"
+                + inputId
+                + '\''
+                + ", maxConcurrentTunedSessions="
+                + maxConcurrentTunedSessions
+                + ", maxConcurrentPlayingSessions="
+                + maxConcurrentPlayingSessions
+                + ", maxConcurrentSessionsOfAllTypes="
+                + maxConcurrentSessionsOfAllTypes
+                + ", playbackWhileRecording="
+                + playbackWhileRecording
+                + '}';
     }
 
     @Override
@@ -122,17 +124,18 @@
         return 0;
     }
 
-    public static final Creator<RecordingCapability> CREATOR = new Creator<RecordingCapability>() {
-        @Override
-        public RecordingCapability createFromParcel(Parcel in) {
-            return new RecordingCapability(in);
-        }
+    public static final Creator<RecordingCapability> CREATOR =
+            new Creator<RecordingCapability>() {
+                @Override
+                public RecordingCapability createFromParcel(Parcel in) {
+                    return new RecordingCapability(in);
+                }
 
-        @Override
-        public RecordingCapability[] newArray(int size) {
-            return new RecordingCapability[size];
-        }
-    };
+                @Override
+                public RecordingCapability[] newArray(int size) {
+                    return new RecordingCapability[size];
+                }
+            };
 
     public static Builder builder() {
         return new Builder();
@@ -171,11 +174,12 @@
         }
 
         public RecordingCapability build() {
-            return new RecordingCapability(mInputId, mMaxConcurrentTunedSessions,
-                    mMaxConcurrentPlayingSessions, mMaxConcurrentSessionsOfAllTypes,
+            return new RecordingCapability(
+                    mInputId,
+                    mMaxConcurrentTunedSessions,
+                    mMaxConcurrentPlayingSessions,
+                    mMaxConcurrentSessionsOfAllTypes,
                     mPlaybackWhileRecording);
         }
     }
 }
-
-
diff --git a/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java
new file mode 100644
index 0000000..8b45a73
--- /dev/null
+++ b/common/src/com/android/tv/common/recording/RecordingStorageStatusManager.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2016 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.common.recording;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Environment;
+import android.os.Looper;
+import android.os.StatFs;
+import android.support.annotation.AnyThread;
+import android.support.annotation.IntDef;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.feature.CommonFeatures;
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/** Signals DVR storage status change such as plugging/unplugging. */
+public class RecordingStorageStatusManager {
+    private static final String TAG = "RecordingStorageStatusManager";
+    private static final boolean DEBUG = false;
+
+    /** Minimum storage size to support DVR */
+    public static final long MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES = 50 * 1024 * 1024 * 1024L; // 50GB
+
+    private static final long MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES =
+            10 * 1024 * 1024 * 1024L; // 10GB
+    private static final String RECORDING_DATA_SUB_PATH = "/recording";
+
+    /** Storage status constants. */
+    @IntDef({
+        STORAGE_STATUS_OK,
+        STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL,
+        STORAGE_STATUS_FREE_SPACE_INSUFFICIENT,
+        STORAGE_STATUS_MISSING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StorageStatus {}
+
+    /** Current storage is OK to record a program. */
+    public static final int STORAGE_STATUS_OK = 0;
+
+    /** Current storage's total capacity is smaller than DVR requirement. */
+    public static final int STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL = 1;
+
+    /** Current storage's free space is insufficient to record programs. */
+    public static final int STORAGE_STATUS_FREE_SPACE_INSUFFICIENT = 2;
+
+    /** Current storage is missing. */
+    public static final int STORAGE_STATUS_MISSING = 3;
+
+    private final Context mContext;
+    private final Set<OnStorageMountChangedListener> mOnStorageMountChangedListeners =
+            new CopyOnWriteArraySet<>();
+    private MountedStorageStatus mMountedStorageStatus;
+    private boolean mStorageValid;
+
+    private class MountedStorageStatus {
+        private final boolean mStorageMounted;
+        private final File mStorageMountedDir;
+        private final long mStorageMountedCapacity;
+
+        private MountedStorageStatus(boolean mounted, File mountedDir, long capacity) {
+            mStorageMounted = mounted;
+            mStorageMountedDir = mountedDir;
+            mStorageMountedCapacity = capacity;
+        }
+
+        private boolean isValidForDvr() {
+            return mStorageMounted && mStorageMountedCapacity >= MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof MountedStorageStatus)) {
+                return false;
+            }
+            MountedStorageStatus status = (MountedStorageStatus) other;
+            return mStorageMounted == status.mStorageMounted
+                    && Objects.equals(mStorageMountedDir, status.mStorageMountedDir)
+                    && mStorageMountedCapacity == status.mStorageMountedCapacity;
+        }
+    }
+
+    public interface OnStorageMountChangedListener {
+
+        /**
+         * Listener for DVR storage status change.
+         *
+         * @param storageMounted {@code true} when DVR possible storage is mounted, {@code false}
+         *     otherwise.
+         */
+        void onStorageMountChanged(boolean storageMounted);
+    }
+
+    private final class StorageStatusBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            MountedStorageStatus result = getStorageStatusInternal();
+            if (mMountedStorageStatus.equals(result)) {
+                return;
+            }
+            mMountedStorageStatus = result;
+            if (result.mStorageMounted) {
+                cleanUpDbIfNeeded();
+            }
+            boolean valid = result.isValidForDvr();
+            if (valid == mStorageValid) {
+                return;
+            }
+            mStorageValid = valid;
+            for (OnStorageMountChangedListener l : mOnStorageMountChangedListeners) {
+                l.onStorageMountChanged(valid);
+            }
+        }
+    }
+
+    /**
+     * Creates RecordingStorageStatusManager.
+     *
+     * @param context {@link Context}
+     */
+    public RecordingStorageStatusManager(final Context context) {
+        mContext = context;
+        mMountedStorageStatus = getStorageStatusInternal();
+        mStorageValid = mMountedStorageStatus.isValidForDvr();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+        filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        filter.addAction(Intent.ACTION_MEDIA_EJECT);
+        filter.addAction(Intent.ACTION_MEDIA_REMOVED);
+        filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
+        filter.addDataScheme(ContentResolver.SCHEME_FILE);
+        mContext.registerReceiver(new StorageStatusBroadcastReceiver(), filter);
+    }
+
+    /**
+     * Adds the listener for receiving storage status change.
+     *
+     * @param listener
+     */
+    public void addListener(OnStorageMountChangedListener listener) {
+        mOnStorageMountChangedListeners.add(listener);
+    }
+
+    /** Removes the current listener. */
+    public void removeListener(OnStorageMountChangedListener listener) {
+        mOnStorageMountChangedListeners.remove(listener);
+    }
+
+    /** Returns true if a storage is mounted. */
+    public boolean isStorageMounted() {
+        return mMountedStorageStatus.mStorageMounted;
+    }
+
+    /** Returns the path to DVR recording data directory. This can take for a while sometimes. */
+    @WorkerThread
+    public File getRecordingRootDataDirectory() {
+        SoftPreconditions.checkState(Looper.myLooper() != Looper.getMainLooper());
+        if (mMountedStorageStatus.mStorageMountedDir == null) {
+            return null;
+        }
+        File root = mContext.getExternalFilesDir(null);
+        String rootPath;
+        try {
+            rootPath = root != null ? root.getCanonicalPath() : null;
+        } catch (IOException | SecurityException e) {
+            return null;
+        }
+        return rootPath == null ? null : new File(rootPath + RECORDING_DATA_SUB_PATH);
+    }
+
+    /**
+     * Returns the current storage status for DVR recordings.
+     *
+     * @return {@link StorageStatus}
+     */
+    @AnyThread
+    public @StorageStatus int getDvrStorageStatus() {
+        MountedStorageStatus status = mMountedStorageStatus;
+        if (status.mStorageMountedDir == null) {
+            return STORAGE_STATUS_MISSING;
+        }
+        if (CommonFeatures.FORCE_RECORDING_UNTIL_NO_SPACE.isEnabled(mContext)) {
+            return STORAGE_STATUS_OK;
+        }
+        if (status.mStorageMountedCapacity < MIN_STORAGE_SIZE_FOR_DVR_IN_BYTES) {
+            return STORAGE_STATUS_TOTAL_CAPACITY_TOO_SMALL;
+        }
+        try {
+            StatFs statFs = new StatFs(status.mStorageMountedDir.toString());
+            if (statFs.getAvailableBytes() < MIN_FREE_STORAGE_SIZE_FOR_DVR_IN_BYTES) {
+                return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT;
+            }
+        } catch (IllegalArgumentException e) {
+            // In rare cases, storage status change was not notified yet.
+            SoftPreconditions.checkState(false);
+            return STORAGE_STATUS_FREE_SPACE_INSUFFICIENT;
+        }
+        return STORAGE_STATUS_OK;
+    }
+
+    /**
+     * Returns whether the storage has sufficient storage.
+     *
+     * @return {@code true} when there is sufficient storage, {@code false} otherwise
+     */
+    public boolean isStorageSufficient() {
+        return getDvrStorageStatus() == STORAGE_STATUS_OK;
+    }
+
+    /** APPs that want to clean up DB for recordings should override this method to do the job. */
+    protected void cleanUpDbIfNeeded() {}
+
+    private MountedStorageStatus getStorageStatusInternal() {
+        boolean storageMounted =
+                Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
+        File storageMountedDir = storageMounted ? Environment.getExternalStorageDirectory() : null;
+        storageMounted = storageMounted && storageMountedDir != null;
+        long storageMountedCapacity = 0L;
+        if (storageMounted) {
+            try {
+                StatFs statFs = new StatFs(storageMountedDir.toString());
+                storageMountedCapacity = statFs.getTotalBytes();
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Storage mount status was changed.");
+                storageMounted = false;
+                storageMountedDir = null;
+            }
+        }
+        return new MountedStorageStatus(storageMounted, storageMountedDir, storageMountedCapacity);
+    }
+}
diff --git a/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java b/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java
index 392d489..6f088c0 100644
--- a/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java
+++ b/common/src/com/android/tv/common/ui/setup/OnActionClickListener.java
@@ -18,14 +18,12 @@
 
 import android.os.Bundle;
 
-/**
- * A listener for the action click.
- */
+/** A listener for the action click. */
 public interface OnActionClickListener {
     /**
      * Called when the action is clicked.
-     * <p>
-     * The method should return {@code true} if the action is handled, otherwise {@code false}.
+     *
+     * <p>The method should return {@code true} if the action is handled, otherwise {@code false}.
      *
      * @param category The action category.
      * @param id The action id.
diff --git a/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java b/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java
index 7ee06fa..8a7dbd7 100644
--- a/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java
+++ b/common/src/com/android/tv/common/ui/setup/SetupActionHelper.java
@@ -22,46 +22,45 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 
-/**
- * Helper class for the execution in the fragment.
- */
+/** Helper class for the execution in the fragment. */
 public class SetupActionHelper {
     private static final String TAG = "SetupActionHelper";
 
-    /**
-     * Executes the action.
-     */
+    /** Executes the action. */
     public static boolean onActionClick(Fragment fragment, String category, int actionId) {
         return onActionClick(fragment, category, actionId, null);
     }
 
-    /**
-     * Executes the action.
-     */
-    public static boolean onActionClick(Fragment fragment, String category, int actionId,
-            Bundle params) {
+    /** Executes the action. */
+    public static boolean onActionClick(
+            Fragment fragment, String category, int actionId, Bundle params) {
         if (fragment.getActivity() instanceof OnActionClickListener) {
-            return ((OnActionClickListener) fragment.getActivity()).onActionClick(category,
-                    actionId, params);
+            return ((OnActionClickListener) fragment.getActivity())
+                    .onActionClick(category, actionId, params);
         }
-        Log.e(TAG, "Activity can't handle the action: {category=" + category + ", actionId="
-                + actionId + ", params=" + params + "}");
+        Log.e(
+                TAG,
+                "Activity can't handle the action: {category="
+                        + category
+                        + ", actionId="
+                        + actionId
+                        + ", params="
+                        + params
+                        + "}");
         return false;
     }
 
-    /**
-     * Creates an {@link OnClickListener} to handle the action.
-     */
-    public static OnClickListener createOnClickListenerForAction(Fragment fragment, String category,
-            int actionId, Bundle params) {
+    /** Creates an {@link OnClickListener} to handle the action. */
+    public static OnClickListener createOnClickListenerForAction(
+            Fragment fragment, String category, int actionId, Bundle params) {
         return new OnActionClickListenerForAction(fragment, category, actionId, params);
     }
 
     /**
      * The {@link OnClickListener} for the view.
-     * <p>
-     * Note that this class should be used only for the views in the {@code mFragment} to avoid the
-     * leak of mFragment.
+     *
+     * <p>Note that this class should be used only for the views in the {@code mFragment} to avoid
+     * the leak of mFragment.
      */
     private static class OnActionClickListenerForAction implements OnClickListener {
         private final Fragment mFragment;
@@ -69,8 +68,8 @@
         private final int mActionId;
         private final Bundle mParams;
 
-        OnActionClickListenerForAction(Fragment fragment, String category, int actionId,
-                Bundle params) {
+        OnActionClickListenerForAction(
+                Fragment fragment, String category, int actionId, Bundle params) {
             mFragment = fragment;
             mCategory = category;
             mActionId = actionId;
@@ -83,5 +82,5 @@
         }
     }
 
-    private SetupActionHelper() { }
+    private SetupActionHelper() {}
 }
diff --git a/common/src/com/android/tv/common/ui/setup/SetupActivity.java b/common/src/com/android/tv/common/ui/setup/SetupActivity.java
index 2b381a6..67418ce 100644
--- a/common/src/com/android/tv/common/ui/setup/SetupActivity.java
+++ b/common/src/com/android/tv/common/ui/setup/SetupActivity.java
@@ -28,7 +28,6 @@
 import android.transition.TransitionInflater;
 import android.view.View;
 import android.view.ViewTreeObserver.OnPreDrawListener;
-
 import com.android.tv.common.R;
 import com.android.tv.common.WeakHandler;
 import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
@@ -51,23 +50,28 @@
         super.onCreate(savedInstanceState);
         SetupAnimationHelper.initialize(this);
         setContentView(R.layout.activity_setup);
-        mFragmentTransitionDuration = getResources().getInteger(
-                R.integer.setup_fragment_transition_duration);
+        mFragmentTransitionDuration =
+                getResources().getInteger(R.integer.setup_fragment_transition_duration);
         // Show initial fragment only when the saved state is not restored, because the last
         // fragment is restored if savesInstanceState is not null.
         if (savedInstanceState == null) {
             // This is the workaround to show the first fragment with delay to show the fragment
             // enter transition. See http://b/26255145
-            getWindow().getDecorView().getViewTreeObserver().addOnPreDrawListener(
-                    new OnPreDrawListener() {
-                        @Override
-                        public boolean onPreDraw() {
-                            getWindow().getDecorView().getViewTreeObserver()
-                                    .removeOnPreDrawListener(this);
-                            showInitialFragment();
-                            return true;
-                        }
-                    });
+            getWindow()
+                    .getDecorView()
+                    .getViewTreeObserver()
+                    .addOnPreDrawListener(
+                            new OnPreDrawListener() {
+                                @Override
+                                public boolean onPreDraw() {
+                                    getWindow()
+                                            .getDecorView()
+                                            .getViewTreeObserver()
+                                            .removeOnPreDrawListener(this);
+                                    showInitialFragment();
+                                    return true;
+                                }
+                            });
         } else {
             mShowInitialFragment = false;
         }
@@ -76,8 +80,8 @@
     /**
      * The inherited class should provide the initial fragment to show.
      *
-     * <p>If this method returns {@code null} during {@link #onCreate}, then call
-     * {@link #showInitialFragment} explicitly later with non null initial fragment.
+     * <p>If this method returns {@code null} during {@link #onCreate}, then call {@link
+     * #showInitialFragment} explicitly later with non null initial fragment.
      */
     protected abstract Fragment onCreateInitialFragment();
 
@@ -98,16 +102,15 @@
         }
     }
 
-    /**
-     * Shows the given fragment.
-     */
+    /** Shows the given fragment. */
     protected FragmentTransaction showFragment(Fragment fragment, boolean addToBackStack) {
         FragmentTransaction ft = getFragmentManager().beginTransaction();
         if (fragment instanceof SetupFragment) {
             int[] sharedElements = ((SetupFragment) fragment).getSharedElementIds();
             if (sharedElements != null && sharedElements.length > 0) {
-                Transition sharedTransition = TransitionInflater.from(this)
-                        .inflateTransition(R.transition.transition_action_background);
+                Transition sharedTransition =
+                        TransitionInflater.from(this)
+                                .inflateTransition(R.transition.transition_action_background);
                 sharedTransition.setDuration(getSharedElementTransitionDuration());
                 SetupAnimationHelper.applyAnimationTimeScale(sharedTransition);
                 fragment.setSharedElementEnterTransition(sharedTransition);
@@ -143,9 +146,9 @@
 
     /**
      * Override this method if the inherited class wants to handle the action.
-     * <p>
-     * The override method should return {@code true} if the action is handled, otherwise
-     * {@code false}.
+     *
+     * <p>The override method should return {@code true} if the action is handled, otherwise {@code
+     * false}.
      */
     protected boolean executeAction(String category, int actionId, Bundle params) {
         return false;
diff --git a/common/src/com/android/tv/common/ui/setup/SetupFragment.java b/common/src/com/android/tv/common/ui/setup/SetupFragment.java
index d2b9d7c..7d47548 100644
--- a/common/src/com/android/tv/common/ui/setup/SetupFragment.java
+++ b/common/src/com/android/tv/common/ui/setup/SetupFragment.java
@@ -26,22 +26,25 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-
 import com.android.tv.common.ui.setup.animation.FadeAndShortSlide;
 import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/**
- * A fragment which slides when it is entering/exiting.
- */
+/** A fragment which slides when it is entering/exiting. */
 public abstract class SetupFragment extends Fragment {
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true,
-            value = {FRAGMENT_ENTER_TRANSITION, FRAGMENT_EXIT_TRANSITION,
-                    FRAGMENT_REENTER_TRANSITION, FRAGMENT_RETURN_TRANSITION})
+    @IntDef(
+        flag = true,
+        value = {
+            FRAGMENT_ENTER_TRANSITION,
+            FRAGMENT_EXIT_TRANSITION,
+            FRAGMENT_REENTER_TRANSITION,
+            FRAGMENT_RETURN_TRANSITION
+        }
+    )
     public @interface FragmentTransitionType {}
+
     public static final int FRAGMENT_ENTER_TRANSITION = 0x01;
     public static final int FRAGMENT_EXIT_TRANSITION = FRAGMENT_ENTER_TRANSITION << 1;
     public static final int FRAGMENT_REENTER_TRANSITION = FRAGMENT_ENTER_TRANSITION << 2;
@@ -49,50 +52,50 @@
 
     private boolean mEnterTransitionRunning;
 
-    private final TransitionListener mTransitionListener = new TransitionListener() {
-        @Override
-        public void onTransitionStart(Transition transition) {
-            mEnterTransitionRunning = true;
-        }
+    private final TransitionListener mTransitionListener =
+            new TransitionListener() {
+                @Override
+                public void onTransitionStart(Transition transition) {
+                    mEnterTransitionRunning = true;
+                }
 
-        @Override
-        public void onTransitionEnd(Transition transition) {
-            mEnterTransitionRunning = false;
-            onEnterTransitionEnd();
-        }
+                @Override
+                public void onTransitionEnd(Transition transition) {
+                    mEnterTransitionRunning = false;
+                    onEnterTransitionEnd();
+                }
 
-        @Override
-        public void onTransitionCancel(Transition transition) { }
+                @Override
+                public void onTransitionCancel(Transition transition) {}
 
-        @Override
-        public void onTransitionPause(Transition transition) { }
+                @Override
+                public void onTransitionPause(Transition transition) {}
 
-        @Override
-        public void onTransitionResume(Transition transition) { }
-    };
+                @Override
+                public void onTransitionResume(Transition transition) {}
+            };
 
-    /**
-     * Returns {@code true} if the enter/reenter transition is running.
-     */
+    /** Returns {@code true} if the enter/reenter transition is running. */
     protected boolean isEnterTransitionRunning() {
         return mEnterTransitionRunning;
     }
 
-    /**
-     * Called when the enter/reenter transition ends.
-     */
-    protected void onEnterTransitionEnd() { }
+    /** Called when the enter/reenter transition ends. */
+    protected void onEnterTransitionEnd() {}
 
     public SetupFragment() {
         setAllowEnterTransitionOverlap(false);
         setAllowReturnTransitionOverlap(false);
-        enableFragmentTransition(FRAGMENT_ENTER_TRANSITION | FRAGMENT_EXIT_TRANSITION
-                | FRAGMENT_REENTER_TRANSITION | FRAGMENT_RETURN_TRANSITION);
+        enableFragmentTransition(
+                FRAGMENT_ENTER_TRANSITION
+                        | FRAGMENT_EXIT_TRANSITION
+                        | FRAGMENT_REENTER_TRANSITION
+                        | FRAGMENT_RETURN_TRANSITION);
     }
 
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         View view = inflater.inflate(getLayoutResourceId(), container, false);
         // After the transition animation, we need to request the focus. If not, this fragment
         // doesn't have the focus.
@@ -100,18 +103,17 @@
         return view;
     }
 
-    /**
-     * Returns the layout resource ID for this fragment.
-     */
-    abstract protected int getLayoutResourceId();
+    /** Returns the layout resource ID for this fragment. */
+    protected abstract int getLayoutResourceId();
 
     protected void setOnClickAction(View view, final String category, final int actionId) {
-        view.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                onActionClick(category, actionId);
-            }
-        });
+        view.setOnClickListener(
+                new OnClickListener() {
+                    @Override
+                    public void onClick(View view) {
+                        onActionClick(category, actionId);
+                    }
+                });
     }
 
     protected boolean onActionClick(String category, int actionId) {
@@ -141,24 +143,22 @@
     /**
      * Enables fragment transition according to the given {@code mask}.
      *
-     * @param mask This value is the combination of {@link #FRAGMENT_ENTER_TRANSITION},
-     * {@link #FRAGMENT_EXIT_TRANSITION}, {@link #FRAGMENT_REENTER_TRANSITION}, and
-     * {@link #FRAGMENT_RETURN_TRANSITION}.
+     * @param mask This value is the combination of {@link #FRAGMENT_ENTER_TRANSITION}, {@link
+     *     #FRAGMENT_EXIT_TRANSITION}, {@link #FRAGMENT_REENTER_TRANSITION}, and {@link
+     *     #FRAGMENT_RETURN_TRANSITION}.
      */
     public void enableFragmentTransition(@FragmentTransitionType int mask) {
-        setEnterTransition((mask & FRAGMENT_ENTER_TRANSITION) == 0 ? null
-                : createTransition(Gravity.END));
-        setExitTransition((mask & FRAGMENT_EXIT_TRANSITION) == 0 ? null
-                : createTransition(Gravity.START));
-        setReenterTransition((mask & FRAGMENT_REENTER_TRANSITION) == 0 ? null
-                : createTransition(Gravity.START));
-        setReturnTransition((mask & FRAGMENT_RETURN_TRANSITION) == 0 ? null
-                : createTransition(Gravity.END));
+        setEnterTransition(
+                (mask & FRAGMENT_ENTER_TRANSITION) == 0 ? null : createTransition(Gravity.END));
+        setExitTransition(
+                (mask & FRAGMENT_EXIT_TRANSITION) == 0 ? null : createTransition(Gravity.START));
+        setReenterTransition(
+                (mask & FRAGMENT_REENTER_TRANSITION) == 0 ? null : createTransition(Gravity.START));
+        setReturnTransition(
+                (mask & FRAGMENT_RETURN_TRANSITION) == 0 ? null : createTransition(Gravity.END));
     }
 
-    /**
-     * Sets the transition with the given {@code slidEdge}.
-     */
+    /** Sets the transition with the given {@code slidEdge}. */
     public void setFragmentTransition(@FragmentTransitionType int transitionType, int slideEdge) {
         switch (transitionType) {
             case FRAGMENT_ENTER_TRANSITION:
@@ -184,9 +184,7 @@
                 .build();
     }
 
-    /**
-     * Changes the move distance of the transitions to short distance.
-     */
+    /** Changes the move distance of the transitions to short distance. */
     public void setShortDistance(@FragmentTransitionType int mask) {
         if ((mask & FRAGMENT_ENTER_TRANSITION) != 0) {
             Transition transition = getEnterTransition();
@@ -218,7 +216,7 @@
      * Returns the ID's of the view's whose descendants will perform delayed move.
      *
      * @see com.android.tv.common.ui.setup.animation.SetupAnimationHelper.TransitionBuilder
-     * #setParentIdsForDelay
+     *     #setParentIdsForDelay
      */
     protected int[] getParentIdsForDelay() {
         return null;
@@ -228,7 +226,7 @@
      * Sets the ID's of the views which will not be included in the transition.
      *
      * @see com.android.tv.common.ui.setup.animation.SetupAnimationHelper.TransitionBuilder
-     * #setExcludeIds
+     *     #setExcludeIds
      */
     protected int[] getExcludedTargetIds() {
         return null;
diff --git a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java
index 7bce9c2..3c76c26 100644
--- a/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java
+++ b/common/src/com/android/tv/common/ui/setup/SetupGuidedStepFragment.java
@@ -16,22 +16,26 @@
 
 package com.android.tv.common.ui.setup;
 
+import static android.content.Context.ACCESSIBILITY_SERVICE;
+
 import android.os.Bundle;
 import android.support.v17.leanback.app.GuidedStepFragment;
 import android.support.v17.leanback.widget.GuidanceStylist;
 import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.support.v17.leanback.widget.VerticalGridView;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.AccessibilityDelegate;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
-
 import com.android.tv.common.R;
 
-/**
- * A fragment for channel source info/setup.
- */
+/** A fragment for channel source info/setup. */
 public abstract class SetupGuidedStepFragment extends GuidedStepFragment {
     /**
      * Key of the argument which indicate whether the parent of this fragment has three panes.
@@ -40,23 +44,30 @@
      */
     public static final String KEY_THREE_PANE = "key_three_pane";
 
+    private View mContentFragment;
+    private boolean mFromContentFragment;
+    private boolean mAccessibilityMode;
+
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         View view = super.onCreateView(inflater, container, savedInstanceState);
         Bundle arguments = getArguments();
         view.findViewById(android.support.v17.leanback.R.id.action_fragment_root)
                 .setPadding(0, 0, 0, 0);
-        LinearLayout.LayoutParams guidanceLayoutParams = (LinearLayout.LayoutParams)
-                view.findViewById(android.support.v17.leanback.R.id.content_fragment)
-                .getLayoutParams();
+        mContentFragment = view.findViewById(android.support.v17.leanback.R.id.content_fragment);
+        LinearLayout.LayoutParams guidanceLayoutParams =
+                (LinearLayout.LayoutParams) mContentFragment.getLayoutParams();
         guidanceLayoutParams.weight = 0;
         if (arguments != null && arguments.getBoolean(KEY_THREE_PANE, false)) {
             // Content fragment.
-            guidanceLayoutParams.width = getResources().getDimensionPixelOffset(
-                    R.dimen.setup_guidedstep_guidance_section_width_3pane);
-            int doneButtonWidth = getResources().getDimensionPixelOffset(
-                    R.dimen.setup_done_button_container_width);
+            guidanceLayoutParams.width =
+                    getResources()
+                            .getDimensionPixelOffset(
+                                    R.dimen.setup_guidedstep_guidance_section_width_3pane);
+            int doneButtonWidth =
+                    getResources()
+                            .getDimensionPixelOffset(R.dimen.setup_done_button_container_width);
             // Guided actions list
             View list = view.findViewById(android.support.v17.leanback.R.id.guidedactions_list);
             MarginLayoutParams marginLayoutParams = (MarginLayoutParams) list.getLayoutParams();
@@ -69,13 +80,16 @@
             }
         } else {
             // Content fragment.
-            guidanceLayoutParams.width = getResources().getDimensionPixelOffset(
-                    R.dimen.setup_guidedstep_guidance_section_width_2pane);
+            guidanceLayoutParams.width =
+                    getResources()
+                            .getDimensionPixelOffset(
+                                    R.dimen.setup_guidedstep_guidance_section_width_2pane);
         }
         // gridView Alignment
         VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView();
-        int offset = getResources().getDimensionPixelOffset(
-                R.dimen.setup_guidedactions_selector_margin_top);
+        int offset =
+                getResources()
+                        .getDimensionPixelOffset(R.dimen.setup_guidedactions_selector_margin_top);
         gridView.setWindowAlignmentOffset(offset);
         gridView.setWindowAlignmentOffsetPercent(0);
         gridView.setItemAlignmentOffsetPercent(0);
@@ -83,19 +97,51 @@
                 .setTransitionGroup(false);
         // Needed for the shared element transition.
         // content_frame is defined in leanback.
-        ViewGroup group = (ViewGroup) view.findViewById(
-                android.support.v17.leanback.R.id.content_frame);
+        ViewGroup group =
+                (ViewGroup) view.findViewById(android.support.v17.leanback.R.id.content_frame);
         group.setClipChildren(false);
         group.setClipToPadding(false);
         return view;
     }
 
     @Override
+    public GuidedActionsStylist onCreateActionsStylist() {
+        return new SetupGuidedStepFragmentGuidedActionsStylist();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        AccessibilityManager am =
+                (AccessibilityManager) getActivity().getSystemService(ACCESSIBILITY_SERVICE);
+        mAccessibilityMode = am != null && am.isEnabled() && am.isTouchExplorationEnabled();
+        mContentFragment.setFocusable(mAccessibilityMode);
+        if (mAccessibilityMode) {
+            mContentFragment.setAccessibilityDelegate(
+                new AccessibilityDelegate() {
+                    @Override
+                    public boolean performAccessibilityAction(View host, int action, Bundle args) {
+                        if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
+                                && !getActions().isEmpty()) {
+                            // scroll to the top. This makes the first action view on the screen.
+                            // Otherwise, the view can be recycled, so accessibility events cannot
+                            // be sent later.
+                            getGuidedActionsStylist().getActionsGridView().scrollToPosition(0);
+                            mFromContentFragment = true;
+                        }
+                        return super.performAccessibilityAction(host, action, args);
+                    }
+                });
+            mContentFragment.requestFocus();
+        }
+    }
+
+    @Override
     public GuidanceStylist onCreateGuidanceStylist() {
         return new GuidanceStylist() {
             @Override
-            public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                    Guidance guidance) {
+            public View onCreateView(
+                    LayoutInflater inflater, ViewGroup container, Guidance guidance) {
                 View view = super.onCreateView(inflater, container, guidance);
                 if (guidance.getIconDrawable() == null) {
                     // Icon view should not take up space when we don't use image.
@@ -106,10 +152,19 @@
         };
     }
 
-    abstract protected String getActionCategory();
+    protected abstract String getActionCategory();
+
+    protected View getDoneButton() {
+        return getActivity().findViewById(R.id.button_done);
+    }
 
     @Override
     public void onGuidedActionClicked(GuidedAction action) {
+        if (!action.isFocusable()) {
+            // an unfocusable action may be clicked in accessibility mode when it's accessibility
+            // focused
+            return;
+        }
         SetupActionHelper.onActionClick(this, getActionCategory(), (int) action.getId());
     }
 
@@ -122,4 +177,38 @@
     public boolean isFocusOutEndAllowed() {
         return true;
     }
+
+    protected void setAccessibilityDelegate(GuidedActionsStylist.ViewHolder vh,
+            GuidedAction action) {
+        if (!mAccessibilityMode || findActionPositionById(action.getId()) == 0) {
+            return;
+        }
+        vh.itemView.setAccessibilityDelegate(
+                new AccessibilityDelegate() {
+                    @Override
+                    public boolean performAccessibilityAction(View host, int action, Bundle args) {
+                        if ((action == AccessibilityNodeInfo.ACTION_FOCUS
+                                || action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)
+                                && mFromContentFragment) {
+                            // block the action and make the first action view accessibility focused
+                            View view = getActionItemView(0);
+                            if (view != null) {
+                                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+                                mFromContentFragment = false;
+                                return true;
+                            }
+                        }
+                        return super.performAccessibilityAction(host, action, args);
+                    }
+                });
+    }
+
+    private class SetupGuidedStepFragmentGuidedActionsStylist extends GuidedActionsStylist {
+
+        @Override
+        public void onBindViewHolder(GuidedActionsStylist.ViewHolder vh, GuidedAction action) {
+            super.onBindViewHolder(vh, action);
+            setAccessibilityDelegate(vh, action);
+        }
+    }
 }
diff --git a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java
index f5c2bf2..c02d3f5 100644
--- a/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java
+++ b/common/src/com/android/tv/common/ui/setup/SetupMultiPaneFragment.java
@@ -23,34 +23,43 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
-
 import com.android.tv.common.R;
 
-/**
- * A fragment for channel source info/setup.
- */
+/** A fragment for channel source info/setup. */
 public abstract class SetupMultiPaneFragment extends SetupFragment {
     private static final String TAG = "SetupMultiPaneFragment";
     private static final boolean DEBUG = false;
 
     public static final int ACTION_DONE = Integer.MAX_VALUE;
     public static final int ACTION_SKIP = ACTION_DONE - 1;
+    public static final int MAX_SUBCLASSES_ID = ACTION_SKIP - 1;
 
-    private static final String CONTENT_FRAGMENT_TAG = "content_fragment";
+    public static final String CONTENT_FRAGMENT_TAG = "content_fragment";
 
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         if (DEBUG) {
-            Log.d(TAG, "onCreateView(" + inflater + ", " + container + ", " + savedInstanceState
-                    + ")");
+            Log.d(
+                    TAG,
+                    "onCreateView("
+                            + inflater
+                            + ", "
+                            + container
+                            + ", "
+                            + savedInstanceState
+                            + ")");
         }
         View view = super.onCreateView(inflater, container, savedInstanceState);
         if (savedInstanceState == null) {
             SetupGuidedStepFragment contentFragment = onCreateContentFragment();
-            getChildFragmentManager().beginTransaction()
-                    .replace(R.id.guided_step_fragment_container, contentFragment,
-                            CONTENT_FRAGMENT_TAG).commit();
+            getChildFragmentManager()
+                    .beginTransaction()
+                    .replace(
+                            R.id.guided_step_fragment_container,
+                            contentFragment,
+                            CONTENT_FRAGMENT_TAG)
+                    .commit();
         }
         if (needsDoneButton()) {
             setOnClickAction(view.findViewById(R.id.button_done), getActionCategory(), ACTION_DONE);
@@ -65,12 +74,12 @@
             if (getResources().getConfiguration().getLayoutDirection()
                     == View.LAYOUT_DIRECTION_LTR) {
                 ((MarginLayoutParams) doneButtonContainer.getLayoutParams()).rightMargin =
-                        -getResources().getDimensionPixelOffset(
-                                R.dimen.setup_done_button_container_width);
+                        -getResources()
+                                .getDimensionPixelOffset(R.dimen.setup_done_button_container_width);
             } else {
                 ((MarginLayoutParams) doneButtonContainer.getLayoutParams()).leftMargin =
-                        -getResources().getDimensionPixelOffset(
-                                R.dimen.setup_done_button_container_width);
+                        -getResources()
+                                .getDimensionPixelOffset(R.dimen.setup_done_button_container_width);
             }
             view.findViewById(R.id.button_done).setFocusable(false);
         }
@@ -82,15 +91,15 @@
         return R.layout.fragment_setup_multi_pane;
     }
 
-    abstract protected SetupGuidedStepFragment onCreateContentFragment();
+    protected abstract SetupGuidedStepFragment onCreateContentFragment();
 
     @Nullable
     protected SetupGuidedStepFragment getContentFragment() {
-        return (SetupGuidedStepFragment) getChildFragmentManager()
-                .findFragmentByTag(CONTENT_FRAGMENT_TAG);
+        return (SetupGuidedStepFragment)
+                getChildFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
     }
 
-    abstract protected String getActionCategory();
+    protected abstract String getActionCategory();
 
     protected boolean needsDoneButton() {
         return true;
@@ -102,13 +111,16 @@
 
     @Override
     protected int[] getParentIdsForDelay() {
-        return new int[] {android.support.v17.leanback.R.id.content_fragment,
-                android.support.v17.leanback.R.id.guidedactions_list};
+        return new int[] {
+            android.support.v17.leanback.R.id.content_fragment,
+            android.support.v17.leanback.R.id.guidedactions_list
+        };
     }
 
     @Override
     public int[] getSharedElementIds() {
-        return new int[] {android.support.v17.leanback.R.id.action_fragment_background,
-                R.id.done_button_container};
+        return new int[] {
+            android.support.v17.leanback.R.id.action_fragment_background, R.id.done_button_container
+        };
     }
 }
diff --git a/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java b/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java
index e1a8e60..60ffb70 100644
--- a/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java
+++ b/common/src/com/android/tv/common/ui/setup/animation/FadeAndShortSlide.java
@@ -29,15 +29,12 @@
 import android.view.ViewParent;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
-/**
- * Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734)
- */
+/** Execute horizontal slide of 1/4 width and fade (to workaround bug 23718734) */
 public class FadeAndShortSlide extends Visibility {
     private static final TimeInterpolator APPEAR_INTERPOLATOR = new DecelerateInterpolator();
     private static final TimeInterpolator DISAPPEAR_INTERPOLATOR = new AccelerateInterpolator();
@@ -48,39 +45,45 @@
 
     private static final int DEFAULT_DISTANCE = 200;
 
-    private static abstract class CalculateSlide {
+    private abstract static class CalculateSlide {
         /** Returns the translation value for view when it goes out of the scene */
-        public abstract float getGoneX(ViewGroup sceneRoot, View view, int[] position,
-                int distance);
+        public abstract float getGoneX(
+                ViewGroup sceneRoot, View view, int[] position, int distance);
     }
 
-    private static final CalculateSlide sCalculateStart = new CalculateSlide() {
-        @Override
-        public float getGoneX(ViewGroup sceneRoot, View view, int[] position, int distance) {
-            final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-            final float x;
-            if (isRtl) {
-                x = view.getTranslationX() + distance;
-            } else {
-                x = view.getTranslationX() - distance;
-            }
-            return x;
-        }
-    };
+    private static final CalculateSlide sCalculateStart =
+            new CalculateSlide() {
+                @Override
+                public float getGoneX(
+                        ViewGroup sceneRoot, View view, int[] position, int distance) {
+                    final boolean isRtl =
+                            sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+                    final float x;
+                    if (isRtl) {
+                        x = view.getTranslationX() + distance;
+                    } else {
+                        x = view.getTranslationX() - distance;
+                    }
+                    return x;
+                }
+            };
 
-    private static final CalculateSlide sCalculateEnd = new CalculateSlide() {
-        @Override
-        public float getGoneX(ViewGroup sceneRoot, View view, int[] position, int distance) {
-            final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-            final float x;
-            if (isRtl) {
-                x = view.getTranslationX() - distance;
-            } else {
-                x = view.getTranslationX() + distance;
-            }
-            return x;
-        }
-    };
+    private static final CalculateSlide sCalculateEnd =
+            new CalculateSlide() {
+                @Override
+                public float getGoneX(
+                        ViewGroup sceneRoot, View view, int[] position, int distance) {
+                    final boolean isRtl =
+                            sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+                    final float x;
+                    if (isRtl) {
+                        x = view.getTranslationX() - distance;
+                    } else {
+                        x = view.getTranslationX() + distance;
+                    }
+                    return x;
+                }
+            };
 
     private static final ViewPositionComparator sViewPositionComparator =
             new ViewPositionComparator();
@@ -131,9 +134,10 @@
         getTransitionTargets((ViewGroup) parentForDelay, transitionTargets);
         sViewPositionComparator.mParentForDelay = parentForDelay;
         sViewPositionComparator.mIsLtr = view.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
-        sViewPositionComparator.mToLeft = sViewPositionComparator.mIsLtr
-                ? mSlideEdge == (appear ? Gravity.END : Gravity.START)
-                : mSlideEdge == (appear ? Gravity.START : Gravity.END);
+        sViewPositionComparator.mToLeft =
+                sViewPositionComparator.mIsLtr
+                        ? mSlideEdge == (appear ? Gravity.END : Gravity.START)
+                        : mSlideEdge == (appear ? Gravity.START : Gravity.END);
         Collections.sort(transitionTargets, sViewPositionComparator);
         return transitionTargets.indexOf(view);
     }
@@ -180,8 +184,8 @@
         captureValues(transitionValues);
         int delayIndex = getDelayOrder(transitionValues.view, false);
         if (delayIndex > 0) {
-            transitionValues.values.put(PROPNAME_DELAY,
-                    delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS);
+            transitionValues.values.put(
+                    PROPNAME_DELAY, delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS);
         }
     }
 
@@ -192,8 +196,8 @@
         captureValues(transitionValues);
         int delayIndex = getDelayOrder(transitionValues.view, true);
         if (delayIndex > 0) {
-            transitionValues.values.put(PROPNAME_DELAY,
-                    delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS);
+            transitionValues.values.put(
+                    PROPNAME_DELAY, delayIndex * SetupAnimationHelper.DELAY_BETWEEN_SIBLINGS_MS);
         }
     }
 
@@ -212,7 +216,10 @@
     }
 
     @Override
-    public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+    public Animator onAppear(
+            ViewGroup sceneRoot,
+            View view,
+            TransitionValues startValues,
             TransitionValues endValues) {
         if (endValues == null) {
             return null;
@@ -221,15 +228,16 @@
         int left = position[0];
         float endX = view.getTranslationX();
         float startX = mSlideCalculator.getGoneX(sceneRoot, view, position, mDistance);
-        final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues,
-                left, startX, endX, APPEAR_INTERPOLATOR, this);
+        final Animator slideAnimator =
+                TranslationAnimationCreator.createAnimation(
+                        view, endValues, left, startX, endX, APPEAR_INTERPOLATOR, this);
         if (slideAnimator == null) {
             return null;
         }
         mFade.setInterpolator(APPEAR_INTERPOLATOR);
         final AnimatorSet set = new AnimatorSet();
         set.play(slideAnimator).with(mFade.onAppear(sceneRoot, view, startValues, endValues));
-        Long delay = (Long ) endValues.values.get(PROPNAME_DELAY);
+        Long delay = (Long) endValues.values.get(PROPNAME_DELAY);
         if (delay != null) {
             set.setStartDelay(delay);
         }
@@ -237,7 +245,10 @@
     }
 
     @Override
-    public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
+    public Animator onDisappear(
+            ViewGroup sceneRoot,
+            final View view,
+            TransitionValues startValues,
             TransitionValues endValues) {
         if (startValues == null) {
             return null;
@@ -246,8 +257,9 @@
         int left = position[0];
         float startX = view.getTranslationX();
         float endX = mSlideCalculator.getGoneX(sceneRoot, view, position, mDistance);
-        final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view,
-                startValues, left, startX, endX, DISAPPEAR_INTERPOLATOR, this);
+        final Animator slideAnimator =
+                TranslationAnimationCreator.createAnimation(
+                        view, startValues, left, startX, endX, DISAPPEAR_INTERPOLATOR, this);
         if (slideAnimator == null) { // slideAnimator is null if startX == endX
             return null;
         }
@@ -257,13 +269,14 @@
         if (fadeAnimator == null) {
             return null;
         }
-        fadeAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                fadeAnimator.removeListener(this);
-                view.setAlpha(0.0f);
-            }
-        });
+        fadeAnimator.addListener(
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animator) {
+                        fadeAnimator.removeListener(this);
+                        view.setAlpha(0.0f);
+                    }
+                });
 
         final AnimatorSet set = new AnimatorSet();
         set.play(slideAnimator).with(fadeAnimator);
@@ -300,9 +313,7 @@
         return super.setDuration(scaledDuration);
     }
 
-    /**
-     * Sets the moving distance in pixel.
-     */
+    /** Sets the moving distance in pixel. */
     public void setDistance(int distance) {
         mDistance = distance;
     }
diff --git a/common/src/com/android/tv/common/ui/setup/animation/SetupAnimationHelper.java b/common/src/com/android/tv/common/ui/setup/animation/SetupAnimationHelper.java
index d98138a..2a913c6 100644
--- a/common/src/com/android/tv/common/ui/setup/animation/SetupAnimationHelper.java
+++ b/common/src/com/android/tv/common/ui/setup/animation/SetupAnimationHelper.java
@@ -27,12 +27,9 @@
 import android.view.Gravity;
 import android.view.View;
 import android.widget.ImageView;
-
 import com.android.tv.common.R;
 
-/**
- * A helper class for setup animation.
- */
+/** A helper class for setup animation. */
 public final class SetupAnimationHelper {
     public static final long DELAY_BETWEEN_SIBLINGS_MS = applyAnimationTimeScale(33);
 
@@ -43,21 +40,21 @@
     private static int sFragmentTransitionLongDistance;
     private static int sFragmentTransitionShortDistance;
 
-    private SetupAnimationHelper() { }
+    private SetupAnimationHelper() {}
 
-    /**
-     * Load initial parameters. This method should be called before using this class.
-     */
+    /** Load initial parameters. This method should be called before using this class. */
     public static void initialize(Context context) {
         if (sInitialized) {
             return;
         }
-        sFragmentTransitionDuration = context.getResources()
-                .getInteger(R.integer.setup_fragment_transition_duration);
-        sFragmentTransitionLongDistance = context.getResources()
-                .getDimensionPixelOffset(R.dimen.setup_fragment_transition_long_distance);
-        sFragmentTransitionShortDistance = context.getResources()
-                .getDimensionPixelOffset(R.dimen.setup_fragment_transition_short_distance);
+        sFragmentTransitionDuration =
+                context.getResources().getInteger(R.integer.setup_fragment_transition_duration);
+        sFragmentTransitionLongDistance =
+                context.getResources()
+                        .getDimensionPixelOffset(R.dimen.setup_fragment_transition_long_distance);
+        sFragmentTransitionShortDistance =
+                context.getResources()
+                        .getDimensionPixelOffset(R.dimen.setup_fragment_transition_short_distance);
         sInitialized = true;
     }
 
@@ -88,9 +85,7 @@
             return this;
         }
 
-        /**
-         * Sets the duration of the transition.
-         */
+        /** Sets the duration of the transition. */
         public TransitionBuilder setDuration(long duration) {
             mDuration = duration;
             return this;
@@ -106,17 +101,13 @@
             return this;
         }
 
-        /**
-         * Sets the ID's of the views which will not be included in the transition.
-         */
+        /** Sets the ID's of the views which will not be included in the transition. */
         public TransitionBuilder setExcludeIds(int[] excludeIds) {
             mExcludeIds = excludeIds;
             return this;
         }
 
-        /**
-         * Builds and returns the {@link android.transition.Transition}.
-         */
+        /** Builds and returns the {@link android.transition.Transition}. */
         public Transition build() {
             FadeAndShortSlide transition = new FadeAndShortSlide(mSlideEdge, mParentIdForDelay);
             transition.setDistance(mDistance);
@@ -130,25 +121,19 @@
         }
     }
 
-    /**
-     * Changes the move distance of the {@code transition} to long distance.
-     */
+    /** Changes the move distance of the {@code transition} to long distance. */
     public static void setLongDistance(FadeAndShortSlide transition) {
         checkInitialized();
         transition.setDistance(sFragmentTransitionLongDistance);
     }
 
-    /**
-     * Changes the move distance of the {@code transition} to short distance.
-     */
+    /** Changes the move distance of the {@code transition} to short distance. */
     public static void setShortDistance(FadeAndShortSlide transition) {
         checkInitialized();
         transition.setDistance(sFragmentTransitionShortDistance);
     }
 
-    /**
-     * Applies the animation scale to the given {@code animator}.
-     */
+    /** Applies the animation scale to the given {@code animator}. */
     public static Animator applyAnimationTimeScale(Animator animator) {
         if (animator instanceof AnimatorSet) {
             for (Animator child : ((AnimatorSet) animator).getChildAnimations()) {
@@ -162,9 +147,7 @@
         return animator;
     }
 
-    /**
-     * Applies the animation scale to the given {@code transition}.
-     */
+    /** Applies the animation scale to the given {@code transition}. */
     public static Transition applyAnimationTimeScale(Transition transition) {
         if (transition instanceof TransitionSet) {
             TransitionSet set = (TransitionSet) transition;
@@ -180,9 +163,7 @@
         return transition;
     }
 
-    /**
-     * Applies the animation scale to the given {@code time}.
-     */
+    /** Applies the animation scale to the given {@code time}. */
     public static long applyAnimationTimeScale(long time) {
         return (long) (time * ANIMATION_TIME_SCALE);
     }
@@ -197,23 +178,25 @@
     }
 
     /**
-     * Returns an animator which animates the source image of the {@link ImageView} with start delay.
+     * Returns an animator which animates the source image of the {@link ImageView} with start
+     * delay.
      *
      * <p>The frame rate is 60 fps.
      */
-    public static ObjectAnimator createFrameAnimatorWithDelay(ImageView imageView, int[] frames,
-            long startDelay) {
+    public static ObjectAnimator createFrameAnimatorWithDelay(
+            ImageView imageView, int[] frames, long startDelay) {
         ObjectAnimator animator = ObjectAnimator.ofInt(imageView, "imageResource", frames);
         // Make it 60 fps.
         animator.setDuration(frames.length * 1000 / 60);
         animator.setInterpolator(null);
         animator.setStartDelay(startDelay);
-        animator.setEvaluator(new TypeEvaluator<Integer>() {
-            @Override
-            public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
-                return startValue;
-            }
-        });
+        animator.setEvaluator(
+                new TypeEvaluator<Integer>() {
+                    @Override
+                    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+                        return startValue;
+                    }
+                });
         return animator;
     }
 
@@ -223,19 +206,20 @@
      * @param view The view which will be animated.
      * @param duration The duration of the animation.
      * @param makeVisibleAfterAnimation If {@code true}, the view will become visible after the
-     * animation ends.
+     *     animation ends.
      */
-    public static Animator createFadeOutAnimator(final View view, long duration,
-            boolean makeVisibleAfterAnimation) {
+    public static Animator createFadeOutAnimator(
+            final View view, long duration, boolean makeVisibleAfterAnimation) {
         ObjectAnimator animator =
                 ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f).setDuration(duration);
         if (makeVisibleAfterAnimation) {
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    view.setAlpha(1.0f);
-                }
-            });
+            animator.addListener(
+                    new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            view.setAlpha(1.0f);
+                        }
+                    });
         }
         return animator;
     }
diff --git a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java
index 7705f7a..13b89ea 100644
--- a/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java
+++ b/common/src/com/android/tv/common/ui/setup/animation/TranslationAnimationCreator.java
@@ -1,3 +1,18 @@
+/*
+ * 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.common.ui.setup.animation;
 
 import android.animation.Animator;
@@ -5,18 +20,16 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.graphics.Path;
+import android.support.v17.leanback.R;
 import android.transition.Transition;
 import android.transition.TransitionValues;
 import android.view.View;
 
-import android.support.v17.leanback.R;
-
 /**
  * This class is used by Slide and Explode to create an animator that goes from the start position
  * to the end position. It takes into account the canceled position so that it will not blink out or
- * shift suddenly when the transition is interrupted.
- * The original class is android.support.v17.leanback.transition.TranslationAnimationCreator which
- * is hidden.
+ * shift suddenly when the transition is interrupted. The original class is
+ * android.support.v17.leanback.transition.TranslationAnimationCreator which is hidden.
  */
 // Copied from android.support.v17.leanback.transition.TransltaionAnimationCreator
 class TranslationAnimationCreator {
@@ -31,11 +44,16 @@
      * @param endX The end translation x of view
      * @param interpolator The interpolator to use with this animator.
      * @return An animator that moves from (startX, startY) to (endX, endY) unless there was a
-     *         previous interruption, in which case it moves from the current position to (endX,
-     *         endY).
+     *     previous interruption, in which case it moves from the current position to (endX, endY).
      */
-    static Animator createAnimation(View view, TransitionValues values, int viewPosX, float startX,
-            float endX, TimeInterpolator interpolator, Transition transition) {
+    static Animator createAnimation(
+            View view,
+            TransitionValues values,
+            int viewPosX,
+            float startX,
+            float endX,
+            TimeInterpolator interpolator,
+            Transition transition) {
         float terminalX = view.getTranslationX();
         Integer startPosition = (Integer) values.view.getTag(R.id.transitionPosition);
         if (startPosition != null) {
@@ -74,8 +92,8 @@
         private float mPausedX;
         private final float mTerminalX;
 
-        private TransitionPositionListener(View movingView, View viewInHierarchy, int startX,
-                float terminalX) {
+        private TransitionPositionListener(
+                View movingView, View viewInHierarchy, int startX, float terminalX) {
             mMovingView = movingView;
             mViewInHierarchy = viewInHierarchy;
             mStartX = startX - Math.round(mMovingView.getTranslationX());
@@ -123,6 +141,4 @@
         @Override
         public void onTransitionResume(Transition transition) {}
     }
-
 }
-
diff --git a/common/src/com/android/tv/common/AutoCloseableUtils.java b/common/src/com/android/tv/common/util/AutoCloseableUtils.java
similarity index 89%
rename from common/src/com/android/tv/common/AutoCloseableUtils.java
rename to common/src/com/android/tv/common/util/AutoCloseableUtils.java
index ad364cc..605715e 100644
--- a/common/src/com/android/tv/common/AutoCloseableUtils.java
+++ b/common/src/com/android/tv/common/util/AutoCloseableUtils.java
@@ -14,17 +14,15 @@
  * limitations under the License
  */
 
-package com.android.tv.common;
+package com.android.tv.common.util;
 
 import android.util.Log;
 
-/**
- * Static utilities for AutoCloseable.
- */
+/** Static utilities for AutoCloseable. */
 public class AutoCloseableUtils {
     private static final String TAG = "AutoCloseableUtils";
 
-    private AutoCloseableUtils() { }
+    private AutoCloseableUtils() {}
 
     public static void closeQuietly(AutoCloseable closeable) {
         try {
diff --git a/common/src/com/android/tv/common/util/Clock.java b/common/src/com/android/tv/common/util/Clock.java
new file mode 100644
index 0000000..cd6ede8
--- /dev/null
+++ b/common/src/com/android/tv/common/util/Clock.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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.common.util;
+
+import android.os.SystemClock;
+
+/**
+ * An interface through which system clocks can be read. The {@link #SYSTEM} implementation must be
+ * used for all non-test cases.
+ */
+public interface Clock {
+    /**
+     * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC.
+     *
+     * @see System#currentTimeMillis().
+     */
+    long currentTimeMillis();
+
+    /**
+     * Returns milliseconds since boot, including time spent in sleep.
+     *
+     * @see SystemClock#elapsedRealtime()
+     */
+    long elapsedRealtime();
+
+    /**
+     * Returns milliseconds since boot, not counting time spent in deep sleep.
+     *
+     * @return milliseconds of non-sleep uptime since boot.
+     * @see SystemClock#uptimeMillis()
+     */
+    long uptimeMillis();
+
+    /**
+     * Waits a given number of milliseconds (of uptimeMillis) before returning.
+     *
+     * @param ms to sleep before returning, in milliseconds of uptime.
+     * @see SystemClock#sleep(long)
+     */
+    void sleep(long ms);
+
+    /** The default implementation of Clock. */
+    Clock SYSTEM =
+            new Clock() {
+                @Override
+                public long currentTimeMillis() {
+                    return System.currentTimeMillis();
+                }
+
+                @Override
+                public long elapsedRealtime() {
+                    return SystemClock.elapsedRealtime();
+                }
+
+                @Override
+                public void sleep(long ms) {
+                    SystemClock.sleep(ms);
+                }
+
+                @Override
+                public long uptimeMillis() {
+                    return SystemClock.uptimeMillis();
+                }
+            };
+}
diff --git a/common/src/com/android/tv/common/CollectionUtils.java b/common/src/com/android/tv/common/util/CollectionUtils.java
similarity index 74%
rename from common/src/com/android/tv/common/CollectionUtils.java
rename to common/src/com/android/tv/common/util/CollectionUtils.java
index 300ad8f..8ca7e3c 100644
--- a/common/src/com/android/tv/common/CollectionUtils.java
+++ b/common/src/com/android/tv/common/util/CollectionUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.tv.common;
+package com.android.tv.common.util;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -23,16 +23,14 @@
 import java.util.Comparator;
 import java.util.List;
 
-/**
- * Static utilities for collections
- */
+/** Static utilities for collections */
 public class CollectionUtils {
 
     /**
      * Returns an array with the arrays concatenated together.
      *
-     * @see <a href="http://stackoverflow.com/a/784842/1122089">Stackoverflow answer</a> by
-     *      <a href="http://stackoverflow.com/users/40342/joachim-sauer">Joachim Sauer</a>
+     * @see <a href="http://stackoverflow.com/a/784842/1122089">Stackoverflow answer</a> by <a
+     *     href="http://stackoverflow.com/users/40342/joachim-sauer">Joachim Sauer</a>
      */
     public static <T> T[] concatAll(T[] first, T[]... rest) {
         int totalLength = first.length;
@@ -50,12 +48,12 @@
 
     /**
      * Unions the two collections and returns the unified list.
-     * <p>
-     * The elements is not compared with hashcode() or equals(). Comparator is used for the equality
-     * check.
+     *
+     * <p>The elements is not compared with hashcode() or equals(). Comparator is used for the
+     * equality check.
      */
-    public static <T> List<T> union(Collection<T> originals, Collection<T> toAdds,
-            Comparator<T> comparator) {
+    public static <T> List<T> union(
+            Collection<T> originals, Collection<T> toAdds, Comparator<T> comparator) {
         List<T> result = new ArrayList<>(originals);
         Collections.sort(result, comparator);
         List<T> resultToAdd = new ArrayList<>();
@@ -68,11 +66,9 @@
         return result;
     }
 
-    /**
-     * Subtracts the elements from the original collection.
-     */
-    public static <T> List<T> subtract(Collection<T> originals, T[] toSubtracts,
-            Comparator<T> comparator) {
+    /** Subtracts the elements from the original collection. */
+    public static <T> List<T> subtract(
+            Collection<T> originals, T[] toSubtracts, Comparator<T> comparator) {
         List<T> result = new ArrayList<>(originals);
         Collections.sort(result, comparator);
         for (T toSubtract : toSubtracts) {
@@ -84,11 +80,9 @@
         return result;
     }
 
-    /**
-     * Returns {@code true} if the two specified collections have common elements.
-     */
-    public static <T> boolean containsAny(Collection<T> c1, Collection<T> c2,
-            Comparator<T> comparator) {
+    /** Returns {@code true} if the two specified collections have common elements. */
+    public static <T> boolean containsAny(
+            Collection<T> c1, Collection<T> c2, Comparator<T> comparator) {
         List<T> contains = new ArrayList<>(c1);
         Collections.sort(contains, comparator);
         for (T iterate : c2) {
diff --git a/common/src/com/android/tv/common/util/CommonUtils.java b/common/src/com/android/tv/common/util/CommonUtils.java
new file mode 100644
index 0000000..305431d
--- /dev/null
+++ b/common/src/com/android/tv/common/util/CommonUtils.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2015 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.common.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.tv.TvInputInfo;
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.Log;
+import com.android.tv.common.BuildConfig;
+import com.android.tv.common.CommonConstants;
+import com.android.tv.common.actions.InputSetupActionUtils;
+import com.android.tv.common.experiments.Experiments;
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Set;
+
+/** Util class for common use in TV app and inputs. */
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
+public final class CommonUtils {
+    private static final String TAG = "CommonUtils";
+    private static final ThreadLocal<SimpleDateFormat> ISO_8601 =
+            new ThreadLocal() {
+                private final SimpleDateFormat value =
+                        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
+
+                @Override
+                protected SimpleDateFormat initialValue() {
+                    return value;
+                }
+            };
+    // Hardcoded list for known bundled inputs not written by OEM/SOCs.
+    // Bundled (system) inputs not in the list will get the high priority
+    // so they and their channels come first in the UI.
+    private static final Set<String> BUNDLED_PACKAGE_SET = new ArraySet<>();
+
+    static {
+        BUNDLED_PACKAGE_SET.add("com.android.tv");
+    }
+
+    private static Boolean sRunningInTest;
+
+    private CommonUtils() {}
+
+    /**
+     * Returns an intent to start the setup activity for the TV input using {@link
+     * InputSetupActionUtils#INTENT_ACTION_INPUT_SETUP}.
+     */
+    public static Intent createSetupIntent(Intent originalSetupIntent, String inputId) {
+        if (originalSetupIntent == null) {
+            return null;
+        }
+        Intent setupIntent = new Intent(originalSetupIntent);
+        if (!InputSetupActionUtils.hasInputSetupAction(originalSetupIntent)) {
+            Intent intentContainer = new Intent(InputSetupActionUtils.INTENT_ACTION_INPUT_SETUP);
+            intentContainer.putExtra(InputSetupActionUtils.EXTRA_SETUP_INTENT, originalSetupIntent);
+            intentContainer.putExtra(InputSetupActionUtils.EXTRA_INPUT_ID, inputId);
+            setupIntent = intentContainer;
+        }
+        return setupIntent;
+    }
+
+    /**
+     * Returns an intent to start the setup activity for this TV input using {@link
+     * InputSetupActionUtils#INTENT_ACTION_INPUT_SETUP}.
+     */
+    public static Intent createSetupIntent(TvInputInfo input) {
+        return createSetupIntent(input.createSetupIntent(), input.getId());
+    }
+
+    /**
+     * Checks if this application is running in tests.
+     *
+     * <p>{@link android.app.ActivityManager#isRunningInTestHarness} doesn't return {@code true} for
+     * the usual devices even the application is running in tests. We need to figure it out by
+     * checking whether the class in tv-tests-common module can be loaded or not.
+     */
+    public static synchronized boolean isRunningInTest() {
+        if (sRunningInTest == null) {
+            try {
+                Class.forName("com.android.tv.testing.utils.Utils");
+                Log.i(
+                        TAG,
+                        "Assumed to be running in a test because"
+                                + " com.android.tv.testing.utils.Utils is found");
+                sRunningInTest = true;
+            } catch (ClassNotFoundException e) {
+                sRunningInTest = false;
+            }
+        }
+        return sRunningInTest;
+    }
+
+    /** Checks whether a given package is in our bundled package set. */
+    public static boolean isInBundledPackageSet(String packageName) {
+        return BUNDLED_PACKAGE_SET.contains(packageName);
+    }
+
+    /** Checks whether a given input is a bundled input. */
+    public static boolean isBundledInput(String inputId) {
+        for (String prefix : BUNDLED_PACKAGE_SET) {
+            if (inputId.startsWith(prefix + "/")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Returns true if the application is packaged with Live TV. */
+    public static boolean isPackagedWithLiveChannels(Context context) {
+        return (CommonConstants.BASE_PACKAGE.equals(context.getPackageName()));
+    }
+
+    /** Returns true if the current user is a developer. */
+    public static boolean isDeveloper() {
+        return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get();
+    }
+
+    /** Converts time in milliseconds to a ISO 8061 string. */
+    public static String toIsoDateTimeString(long timeMillis) {
+        return ISO_8601.get().format(new Date(timeMillis));
+    }
+
+    /** Deletes a file or a directory. */
+    public static void deleteDirOrFile(File fileOrDirectory) {
+        if (fileOrDirectory.isDirectory()) {
+            for (File child : fileOrDirectory.listFiles()) {
+                deleteDirOrFile(child);
+            }
+        }
+        fileOrDirectory.delete();
+    }
+
+    public static boolean isRoboTest() {
+        return "robolectric".equals(Build.FINGERPRINT);
+    }
+}
diff --git a/common/src/com/android/tv/common/util/ContentUriUtils.java b/common/src/com/android/tv/common/util/ContentUriUtils.java
new file mode 100644
index 0000000..6cbe5e1
--- /dev/null
+++ b/common/src/com/android/tv/common/util/ContentUriUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 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.common.util;
+
+import android.content.ContentUris;
+import android.net.Uri;
+import android.util.Log;
+
+/** Static utils for{@link android.content.ContentUris}. */
+public class ContentUriUtils {
+    private static final String TAG = "ContentUriUtils";
+
+    /**
+     * Converts the last path segment to a long.
+     *
+     * <p>This supports a common convention for content URIs where an ID is stored in the last
+     * segment.
+     *
+     * @return the long conversion of the last segment or -1 if the path is empty or there is any
+     *     error
+     * @see ContentUris#parseId(Uri)
+     */
+    public static long safeParseId(Uri uri) {
+        try {
+            return ContentUris.parseId(uri);
+        } catch (Exception e) {
+            Log.d(TAG, "Error parsing " + uri, e);
+            return -1;
+        }
+    }
+}
diff --git a/src/com/android/tv/util/Debug.java b/common/src/com/android/tv/common/util/Debug.java
similarity index 74%
rename from src/com/android/tv/util/Debug.java
rename to common/src/com/android/tv/common/util/Debug.java
index 67a2683..ab90874 100644
--- a/src/com/android/tv/util/Debug.java
+++ b/common/src/com/android/tv/common/util/Debug.java
@@ -14,34 +14,26 @@
  * limitations under the License.
  */
 
-package com.android.tv.util;
+package com.android.tv.common.util;
 
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
-/**
- * A class only for help developers.
- */
+/** A class only for help developers. */
 public class Debug {
     /**
-     * A threshold of start up time, when the start up time of Live TV is more than it,
-     * a warning will show to the developer.
+     * A threshold of start up time, when the start up time of Live TV is more than it, a
+     * warning will show to the developer.
      */
     public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6);
-    /**
-     * Tag for measuring start up time of Live TV.
-     */
+    /** Tag for measuring start up time of Live TV. */
     public static final String TAG_START_UP_TIMER = "start_up_timer";
 
-    /**
-     * A global map for duration timers.
-     */
-    private final static Map<String, DurationTimer> sTimerMap = new HashMap<>();
+    /** A global map for duration timers. */
+    private static final Map<String, DurationTimer> sTimerMap = new HashMap<>();
 
-    /**
-     * Returns the global duration timer by tag.
-     */
+    /** Returns the global duration timer by tag. */
     public static DurationTimer getTimer(String tag) {
         if (sTimerMap.get(tag) != null) {
             return sTimerMap.get(tag);
@@ -51,9 +43,7 @@
         return timer;
     }
 
-    /**
-     * Removes the global duration timer by tag.
-     */
+    /** Removes the global duration timer by tag. */
     public static DurationTimer removeTimer(String tag) {
         return sTimerMap.remove(tag);
     }
diff --git a/src/com/android/tv/util/DurationTimer.java b/common/src/com/android/tv/common/util/DurationTimer.java
similarity index 86%
rename from src/com/android/tv/util/DurationTimer.java
rename to common/src/com/android/tv/common/util/DurationTimer.java
index 1f057bf..91581ad 100644
--- a/src/com/android/tv/util/DurationTimer.java
+++ b/common/src/com/android/tv/common/util/DurationTimer.java
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.tv.util;
+package com.android.tv.common.util;
 
 import android.os.SystemClock;
 import android.util.Log;
-
 import com.android.tv.common.BuildConfig;
 
-/**
- * Times a duration.
- */
+/** Times a duration. */
 public final class DurationTimer {
     private static final String TAG = "DurationTimer";
     public static final long TIME_NOT_SET = -1;
@@ -32,30 +29,24 @@
     private String mTag = TAG;
     private boolean mLogEngOnly;
 
-    public DurationTimer() { }
+    public DurationTimer() {}
 
     public DurationTimer(String tag, boolean logEngOnly) {
         mTag = tag;
         mLogEngOnly = logEngOnly;
     }
 
-    /**
-     * Returns true if the timer is running.
-     */
+    /** Returns true if the timer is running. */
     public boolean isRunning() {
         return mStartTimeMs != TIME_NOT_SET;
     }
 
-    /**
-     * Start the timer.
-     */
+    /** Start the timer. */
     public void start() {
         mStartTimeMs = SystemClock.elapsedRealtime();
     }
 
-    /**
-     * Returns true if timer is started.
-     */
+    /** Returns true if timer is started. */
     public boolean isStarted() {
         return mStartTimeMs != TIME_NOT_SET;
     }
@@ -72,7 +63,7 @@
      * Stops the timer and resets its value to {@link #TIME_NOT_SET}.
      *
      * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not
-     * running.
+     *     running.
      */
     public long reset() {
         long duration = getDuration();
@@ -80,9 +71,7 @@
         return duration;
     }
 
-    /**
-     * Adds information and duration time to the log.
-     */
+    /** Adds information and duration time to the log. */
     public void log(String message) {
         if (isRunning() && (!mLogEngOnly || BuildConfig.ENG)) {
             Log.i(mTag, message + " : " + getDuration() + "ms");
diff --git a/src/com/android/tv/util/LocationUtils.java b/common/src/com/android/tv/common/util/LocationUtils.java
similarity index 76%
rename from src/com/android/tv/util/LocationUtils.java
rename to common/src/com/android/tv/common/util/LocationUtils.java
index d5d7bee..5315529 100644
--- a/src/com/android/tv/util/LocationUtils.java
+++ b/common/src/com/android/tv/common/util/LocationUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.tv.util;
+package com.android.tv.common.util;
 
 import android.Manifest;
 import android.content.Context;
@@ -28,16 +28,16 @@
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Log;
+import com.android.tv.common.BuildConfig;
 
-import com.android.tv.tuner.util.PostalCodeUtils;
+
+
 
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
 
-/**
- * A utility class to get the current location.
- */
+/** A utility class to get the current location. */
 public class LocationUtils {
     private static final String TAG = "LocationUtils";
     private static final boolean DEBUG = false;
@@ -47,11 +47,9 @@
     private static String sCountry;
     private static IOException sError;
 
-    /**
-     * Checks the current location.
-     */
-    public static synchronized Address getCurrentAddress(Context context) throws IOException,
-            SecurityException {
+    /** Checks the current location. */
+    public static synchronized Address getCurrentAddress(Context context)
+            throws IOException, SecurityException {
         if (sAddress != null) {
             return sAddress;
         }
@@ -84,8 +82,8 @@
         }
         Geocoder geocoder = new Geocoder(sApplicationContext, Locale.getDefault());
         try {
-            List<Address> addresses = geocoder.getFromLocation(
-                    location.getLatitude(), location.getLongitude(), 1);
+            List<Address> addresses =
+                    geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
             if (addresses != null && !addresses.isEmpty()) {
                 sAddress = addresses.get(0);
                 if (DEBUG) Log.d(TAG, "Got " + sAddress);
@@ -104,31 +102,33 @@
         }
     }
 
-    private LocationUtils() { }
+    private LocationUtils() {}
 
     private static class LocationUtilsHelper {
-        private static final LocationListener LOCATION_LISTENER = new LocationListener() {
-            @Override
-            public void onLocationChanged(Location location) {
-                updateAddress(location);
-            }
+        private static final LocationListener LOCATION_LISTENER =
+                new LocationListener() {
+                    @Override
+                    public void onLocationChanged(Location location) {
+                        updateAddress(location);
+                    }
 
-            @Override
-            public void onStatusChanged(String provider, int status, Bundle extras) { }
+                    @Override
+                    public void onStatusChanged(String provider, int status, Bundle extras) {}
 
-            @Override
-            public void onProviderEnabled(String provider) { }
+                    @Override
+                    public void onProviderEnabled(String provider) {}
 
-            @Override
-            public void onProviderDisabled(String provider) { }
-        };
+                    @Override
+                    public void onProviderDisabled(String provider) {}
+                };
 
         private static LocationManager sLocationManager;
 
         public static void startLocationUpdates() {
             if (sLocationManager == null) {
-                sLocationManager = (LocationManager) sApplicationContext.getSystemService(
-                        Context.LOCATION_SERVICE);
+                sLocationManager =
+                        (LocationManager)
+                                sApplicationContext.getSystemService(Context.LOCATION_SERVICE);
                 try {
                     sLocationManager.requestLocationUpdates(
                             LocationManager.NETWORK_PROVIDER, 1000, 10, LOCATION_LISTENER, null);
diff --git a/src/com/android/tv/util/NetworkTrafficTags.java b/common/src/com/android/tv/common/util/NetworkTrafficTags.java
similarity index 97%
rename from src/com/android/tv/util/NetworkTrafficTags.java
rename to common/src/com/android/tv/common/util/NetworkTrafficTags.java
index 2dca613..91f2bcd 100644
--- a/src/com/android/tv/util/NetworkTrafficTags.java
+++ b/common/src/com/android/tv/common/util/NetworkTrafficTags.java
@@ -14,11 +14,10 @@
  * limitations under the License
  */
 
-package com.android.tv.util;
+package com.android.tv.common.util;
 
 import android.net.TrafficStats;
 import android.support.annotation.NonNull;
-
 import java.util.concurrent.Executor;
 
 /** Constants for tagging network traffic in the Live channels app. */
diff --git a/common/src/com/android/tv/common/util/PermissionUtils.java b/common/src/com/android/tv/common/util/PermissionUtils.java
new file mode 100644
index 0000000..8d409e5
--- /dev/null
+++ b/common/src/com/android/tv/common/util/PermissionUtils.java
@@ -0,0 +1,68 @@
+/*
+ * 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.common.util;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+/** Util class to handle permissions. */
+public class PermissionUtils {
+    /** Permission to read the TV listings. */
+    public static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
+
+    private static Boolean sHasAccessAllEpgPermission;
+    private static Boolean sHasAccessWatchedHistoryPermission;
+    private static Boolean sHasModifyParentalControlsPermission;
+
+    public static boolean hasAccessAllEpg(Context context) {
+        if (sHasAccessAllEpgPermission == null) {
+            sHasAccessAllEpgPermission =
+                    context.checkSelfPermission(
+                                    "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA")
+                            == PackageManager.PERMISSION_GRANTED;
+        }
+        return sHasAccessAllEpgPermission;
+    }
+
+    public static boolean hasAccessWatchedHistory(Context context) {
+        if (sHasAccessWatchedHistoryPermission == null) {
+            sHasAccessWatchedHistoryPermission =
+                    context.checkSelfPermission(
+                                    "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS")
+                            == PackageManager.PERMISSION_GRANTED;
+        }
+        return sHasAccessWatchedHistoryPermission;
+    }
+
+    public static boolean hasModifyParentalControls(Context context) {
+        if (sHasModifyParentalControlsPermission == null) {
+            sHasModifyParentalControlsPermission =
+                    context.checkSelfPermission("android.permission.MODIFY_PARENTAL_CONTROLS")
+                            == PackageManager.PERMISSION_GRANTED;
+        }
+        return sHasModifyParentalControlsPermission;
+    }
+
+    public static boolean hasReadTvListings(Context context) {
+        return context.checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    public static boolean hasInternet(Context context) {
+        return context.checkSelfPermission("android.permission.INTERNET")
+                == PackageManager.PERMISSION_GRANTED;
+    }
+}
diff --git a/src/com/android/tv/tuner/util/PostalCodeUtils.java b/common/src/com/android/tv/common/util/PostalCodeUtils.java
similarity index 88%
rename from src/com/android/tv/tuner/util/PostalCodeUtils.java
rename to common/src/com/android/tv/common/util/PostalCodeUtils.java
index 9eb689a..c0917af 100644
--- a/src/com/android/tv/tuner/util/PostalCodeUtils.java
+++ b/common/src/com/android/tv/common/util/PostalCodeUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.tv.tuner.util;
+package com.android.tv.common.util;
 
 import android.content.Context;
 import android.location.Address;
@@ -22,18 +22,14 @@
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.Log;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.util.LocationUtils;
-
+import com.android.tv.common.CommonPreferences;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Pattern;
 
-/**
- * A utility class to update, get, and set the last known postal or zip code.
- */
+/** A utility class to update, get, and set the last known postal or zip code. */
 public class PostalCodeUtils {
     private static final String TAG = "PostalCodeUtils";
 
@@ -84,7 +80,7 @@
      * input by users.
      */
     public static String getLastPostalCode(Context context) {
-        return TunerPreferences.getLastPostalCode(context);
+        return CommonPreferences.getLastPostalCode(context);
     }
 
     /**
@@ -93,15 +89,19 @@
      */
     public static void setLastPostalCode(Context context, String postalCode) {
         Log.i(TAG, "Set Postal Code:" + postalCode);
-        TunerPreferences.setLastPostalCode(context, postalCode);
+        CommonPreferences.setLastPostalCode(context, postalCode);
     }
 
     @Nullable
     private static String getPostalCode(Context context) throws IOException, SecurityException {
         Address address = LocationUtils.getCurrentAddress(context);
         if (address != null) {
-            Log.i(TAG, "Current country and postal code is " + address.getCountryName() + ", "
-                    + address.getPostalCode());
+            Log.i(
+                    TAG,
+                    "Current country and postal code is "
+                            + address.getCountryName()
+                            + ", "
+                            + address.getPostalCode());
             return address.getPostalCode();
         }
         return null;
@@ -109,8 +109,7 @@
 
     /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */
     public static class NoPostalCodeException extends Exception {
-        public NoPostalCodeException() {
-        }
+        public NoPostalCodeException() {}
     }
 
     /**
@@ -135,4 +134,4 @@
                 REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase());
         return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength;
     }
-}
\ No newline at end of file
+}
diff --git a/common/src/com/android/tv/common/SharedPreferencesUtils.java b/common/src/com/android/tv/common/util/SharedPreferencesUtils.java
similarity index 75%
rename from common/src/com/android/tv/common/SharedPreferencesUtils.java
rename to common/src/com/android/tv/common/util/SharedPreferencesUtils.java
index 140c4e6..e8bfe61 100644
--- a/common/src/com/android/tv/common/SharedPreferencesUtils.java
+++ b/common/src/com/android/tv/common/util/SharedPreferencesUtils.java
@@ -14,15 +14,14 @@
  * limitations under the License
  */
 
-package com.android.tv.common;
+package com.android.tv.common.util;
 
 import android.content.Context;
 import android.os.AsyncTask;
 import android.preference.PreferenceManager;
+import com.android.tv.common.CommonConstants;
 
-/**
- * Static utilities for {@link android.content.SharedPreferences}
- */
+/** Static utilities for {@link android.content.SharedPreferences} */
 public final class SharedPreferencesUtils {
     // Note that changing the preference name will reset the preference values.
     public static final String SHARED_PREF_FEATURES = "sharePreferencesFeatures";
@@ -31,7 +30,7 @@
     public static final String SHARED_PREF_DVR_WATCHED_POSITION =
             "dvr_watched_position_shared_preference";
     public static final String SHARED_PREF_AUDIO_CAPABILITIES =
-            "com.android.tv.audio_capabilities";
+            CommonConstants.BASE_PACKAGE + ".audio_capabilities";
     public static final String SHARED_PREF_RECURRING_RUNNER = "sharedPreferencesRecurringRunner";
     public static final String SHARED_PREF_EPG = "epg_preferences";
     public static final String SHARED_PREF_SERIES_RECORDINGS = "seriesRecordings";
@@ -39,15 +38,16 @@
     public static final String SHARED_PREF_CHANNEL_LOGO_URIS = "channelLogoUris";
     /** Stores the UI related settings */
     public static final String SHARED_PREF_UI_SETTINGS = "ui_settings";
+
     public static final String SHARED_PREF_PREVIEW_PROGRAMS = "previewPrograms";
 
     private static boolean sInitializeCalled;
 
     /**
-     * {@link android.content.SharedPreferences} loads the preference file when
-     * {@link Context#getSharedPreferences(String, int)} is called for the first time.
-     * Call {@link Context#getSharedPreferences(String, int)} as early as possible to avoid the ANR
-     * due to the file loading.
+     * {@link android.content.SharedPreferences} loads the preference file when {@link
+     * Context#getSharedPreferences(String, int)} is called for the first time. Call {@link
+     * Context#getSharedPreferences(String, int)} as early as possible to avoid the ANR due to the
+     * file loading.
      */
     public static synchronized void initialize(final Context context, final Runnable postTask) {
         if (!sInitializeCalled) {
@@ -59,15 +59,15 @@
                     context.getSharedPreferences(SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
                     context.getSharedPreferences(SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE);
                     context.getSharedPreferences(SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE);
-                    context.getSharedPreferences(SHARED_PREF_DVR_WATCHED_POSITION,
-                            Context.MODE_PRIVATE);
-                    context.getSharedPreferences(SHARED_PREF_AUDIO_CAPABILITIES,
-                            Context.MODE_PRIVATE);
-                    context.getSharedPreferences(SHARED_PREF_RECURRING_RUNNER,
-                            Context.MODE_PRIVATE);
+                    context.getSharedPreferences(
+                            SHARED_PREF_DVR_WATCHED_POSITION, Context.MODE_PRIVATE);
+                    context.getSharedPreferences(
+                            SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE);
+                    context.getSharedPreferences(
+                            SHARED_PREF_RECURRING_RUNNER, Context.MODE_PRIVATE);
                     context.getSharedPreferences(SHARED_PREF_EPG, Context.MODE_PRIVATE);
-                    context.getSharedPreferences(SHARED_PREF_SERIES_RECORDINGS,
-                            Context.MODE_PRIVATE);
+                    context.getSharedPreferences(
+                            SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE);
                     context.getSharedPreferences(SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE);
                     return null;
                 }
@@ -80,5 +80,5 @@
         }
     }
 
-    private SharedPreferencesUtils() { }
+    private SharedPreferencesUtils() {}
 }
diff --git a/src/com/android/tv/util/StringUtils.java b/common/src/com/android/tv/common/util/StringUtils.java
similarity index 79%
rename from src/com/android/tv/util/StringUtils.java
rename to common/src/com/android/tv/common/util/StringUtils.java
index 659807e..b946142 100644
--- a/src/com/android/tv/util/StringUtils.java
+++ b/common/src/com/android/tv/common/util/StringUtils.java
@@ -14,18 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.tv.util;
+package com.android.tv.common.util;
 
-/**
- * Utility class for handling {@link String}.
- */
+/** Utility class for handling {@link String}. */
 public final class StringUtils {
 
-    private StringUtils() { }
+    private StringUtils() {}
 
-    /**
-     * Returns compares two strings lexicographically and handles null values quietly.
-     */
+    /** Returns compares two strings lexicographically and handles null values quietly. */
     public static int compare(String a, String b) {
         if (a == null) {
             return b == null ? 0 : -1;
diff --git a/common/src/com/android/tv/common/util/SystemProperties.java b/common/src/com/android/tv/common/util/SystemProperties.java
new file mode 100644
index 0000000..a9f18d4
--- /dev/null
+++ b/common/src/com/android/tv/common/util/SystemProperties.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.common.util;
+
+import com.android.tv.common.BooleanSystemProperty;
+
+/** A convenience class for getting TV related system properties. */
+public final class SystemProperties {
+
+    /** Allow Google Analytics for eng builds. */
+    public static final BooleanSystemProperty ALLOW_ANALYTICS_IN_ENG =
+            new BooleanSystemProperty("tv_allow_analytics_in_eng", false);
+
+    /** Allow Strict mode for debug builds. */
+    public static final BooleanSystemProperty ALLOW_STRICT_MODE =
+            new BooleanSystemProperty("tv_allow_strict_mode", true);
+
+    /** When true {@link android.view.KeyEvent}s are logged. Defaults to false. */
+    public static final BooleanSystemProperty LOG_KEYEVENT =
+            new BooleanSystemProperty("tv_log_keyevent", false);
+    /** When true debug keys are used. Defaults to false. */
+    public static final BooleanSystemProperty USE_DEBUG_KEYS =
+            new BooleanSystemProperty("tv_use_debug_keys", false);
+
+    /** Send {@link com.android.tv.analytics.Tracker} information. Defaults to {@code true}. */
+    public static final BooleanSystemProperty USE_TRACKER =
+            new BooleanSystemProperty("tv_use_tracker", true);
+
+    static {
+        updateSystemProperties();
+    }
+
+    private SystemProperties() {}
+
+    /** Update the TV related system properties. */
+    public static void updateSystemProperties() {
+        BooleanSystemProperty.resetAll();
+    }
+}
diff --git a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java b/common/src/com/android/tv/common/util/SystemPropertiesProxy.java
similarity index 75%
rename from src/com/android/tv/tuner/util/SystemPropertiesProxy.java
rename to common/src/com/android/tv/common/util/SystemPropertiesProxy.java
index 2817ccb..a3ffd0f 100644
--- a/src/com/android/tv/tuner/util/SystemPropertiesProxy.java
+++ b/common/src/com/android/tv/common/util/SystemPropertiesProxy.java
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.tv.tuner.util;
+package com.android.tv.common.util;
 
 import android.util.Log;
-
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
@@ -27,32 +26,35 @@
 public class SystemPropertiesProxy {
     private static final String TAG = "SystemPropertiesProxy";
 
-    private SystemPropertiesProxy() { }
+    private SystemPropertiesProxy() {}
 
-    public static boolean getBoolean(String key, boolean def)
-            throws IllegalArgumentException {
+    public static boolean getBoolean(String key, boolean def) throws IllegalArgumentException {
         try {
             Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
-            Method getBooleanMethod = SystemPropertiesClass.getDeclaredMethod("getBoolean",
-                    String.class, boolean.class);
+            Method getBooleanMethod =
+                    SystemPropertiesClass.getDeclaredMethod(
+                            "getBoolean", String.class, boolean.class);
             getBooleanMethod.setAccessible(true);
             return (boolean) getBooleanMethod.invoke(SystemPropertiesClass, key, def);
-        } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException
+        } catch (InvocationTargetException
+                | IllegalAccessException
+                | NoSuchMethodException
                 | ClassNotFoundException e) {
             Log.e(TAG, "Failed to invoke SystemProperties.getBoolean()", e);
         }
         return def;
     }
 
-    public static int getInt(String key, int def)
-            throws IllegalArgumentException {
+    public static int getInt(String key, int def) throws IllegalArgumentException {
         try {
             Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
-            Method getIntMethod = SystemPropertiesClass.getDeclaredMethod("getInt",
-                    String.class, int.class);
+            Method getIntMethod =
+                    SystemPropertiesClass.getDeclaredMethod("getInt", String.class, int.class);
             getIntMethod.setAccessible(true);
             return (int) getIntMethod.invoke(SystemPropertiesClass, key, def);
-        } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException
+        } catch (InvocationTargetException
+                | IllegalAccessException
+                | NoSuchMethodException
                 | ClassNotFoundException e) {
             Log.e(TAG, "Failed to invoke SystemProperties.getInt()", e);
         }
diff --git a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java
deleted file mode 100644
index 39196fe..0000000
--- a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/SCSU.java
+++ /dev/null
@@ -1,186 +0,0 @@
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-/*
- *******************************************************************************
- * Copyright (C) 1996-2007, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
- *******************************************************************************
- */
-
-package com.ibm.icu.text;
-
-/**
- * An interface defining  constants for the Standard Compression Scheme
- * for Unicode (SCSU) as outlined in <A
- * HREF="http://www.unicode.org/unicode/reports/tr6">Unicode Technical
- * Report #6</A>.
- *
- * @author Stephen F. Booth
- * @version 1.1 05 Aug 99
- * @version 1.0 26 Jul 99
- */
-interface SCSU
-{
-    //==========================
-    // Generic window shift
-    //==========================
-    final static int COMPRESSIONOFFSET           = 0x80;
-
-    //==========================
-    // Number of windows
-    //==========================
-    final static int NUMWINDOWS                  = 8;
-    final static int NUMSTATICWINDOWS            = 8;
-
-    //==========================
-    // Indicates a window index is invalid
-    //==========================
-    final static int INVALIDWINDOW               = -1;
-
-    //==========================
-    // Indicates a character doesn't exist in input (past end of buffer)
-    //==========================
-    final static int INVALIDCHAR                 = -1;
-
-    //==========================
-    // Compression modes
-    //==========================
-    final static int SINGLEBYTEMODE              = 0;
-    final static int UNICODEMODE                 = 1;
-
-    //==========================
-    // Maximum value for a window's index
-    //==========================
-    final static int MAXINDEX                    = 0xFF;
-
-    //==========================
-    // Reserved index value (characters belongs to first block)
-    //==========================
-    final static int RESERVEDINDEX               = 0x00;
-
-    //==========================
-    // Indices for scripts which cross a half-block boundary
-    //==========================
-    final static int LATININDEX                  = 0xF9;
-    final static int IPAEXTENSIONINDEX           = 0xFA;
-    final static int GREEKINDEX                  = 0xFB;
-    final static int ARMENIANINDEX               = 0xFC;
-    final static int HIRAGANAINDEX               = 0xFD;
-    final static int KATAKANAINDEX               = 0xFE;
-    final static int HALFWIDTHKATAKANAINDEX      = 0xFF;
-
-    //==========================
-    // Single-byte mode tags
-    //==========================
-    final static int SDEFINEX                    = 0x0B;
-    final static int SRESERVED                   = 0x0C;  // reserved value
-    final static int SQUOTEU                     = 0x0E;
-    final static int SCHANGEU                    = 0x0F;
-
-    final static int SQUOTE0                     = 0x01;
-    final static int SQUOTE1                     = 0x02;
-    final static int SQUOTE2                     = 0x03;
-    final static int SQUOTE3                     = 0x04;
-    final static int SQUOTE4                     = 0x05;
-    final static int SQUOTE5                     = 0x06;
-    final static int SQUOTE6                     = 0x07;
-    final static int SQUOTE7                     = 0x08;
-
-    final static int SCHANGE0                    = 0x10;
-    final static int SCHANGE1                    = 0x11;
-    final static int SCHANGE2                    = 0x12;
-    final static int SCHANGE3                    = 0x13;
-    final static int SCHANGE4                    = 0x14;
-    final static int SCHANGE5                    = 0x15;
-    final static int SCHANGE6                    = 0x16;
-    final static int SCHANGE7                    = 0x17;
-
-    final static int SDEFINE0                    = 0x18;
-    final static int SDEFINE1                    = 0x19;
-    final static int SDEFINE2                    = 0x1A;
-    final static int SDEFINE3                    = 0x1B;
-    final static int SDEFINE4                    = 0x1C;
-    final static int SDEFINE5                    = 0x1D;
-    final static int SDEFINE6                    = 0x1E;
-    final static int SDEFINE7                    = 0x1F;
-
-    //==========================
-    // Unicode mode tags
-    //==========================
-    final static int UCHANGE0                    = 0xE0;
-    final static int UCHANGE1                    = 0xE1;
-    final static int UCHANGE2                    = 0xE2;
-    final static int UCHANGE3                    = 0xE3;
-    final static int UCHANGE4                    = 0xE4;
-    final static int UCHANGE5                    = 0xE5;
-    final static int UCHANGE6                    = 0xE6;
-    final static int UCHANGE7                    = 0xE7;
-
-    final static int UDEFINE0                    = 0xE8;
-    final static int UDEFINE1                    = 0xE9;
-    final static int UDEFINE2                    = 0xEA;
-    final static int UDEFINE3                    = 0xEB;
-    final static int UDEFINE4                    = 0xEC;
-    final static int UDEFINE5                    = 0xED;
-    final static int UDEFINE6                    = 0xEE;
-    final static int UDEFINE7                    = 0xEF;
-
-    final static int UQUOTEU                     = 0xF0;
-    final static int UDEFINEX                    = 0xF1;
-    final static int URESERVED                   = 0xF2;  // reserved value
-
-
-    //==========================
-    // Class variables
-    //==========================
-
-    /** For window offset mapping */
-    final static int [] sOffsetTable = { 
-        // table generated by CompressionTableGenerator
-        0x0, 0x80, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, 0x400,
-    0x480, 0x500, 0x580, 0x600, 0x680, 0x700, 0x780, 0x800, 0x880,
-    0x900, 0x980, 0xa00, 0xa80, 0xb00, 0xb80, 0xc00, 0xc80, 0xd00,
-    0xd80, 0xe00, 0xe80, 0xf00, 0xf80, 0x1000, 0x1080, 0x1100,
-    0x1180, 0x1200, 0x1280, 0x1300, 0x1380, 0x1400, 0x1480,
-    0x1500, 0x1580, 0x1600, 0x1680, 0x1700, 0x1780, 0x1800,
-    0x1880, 0x1900, 0x1980, 0x1a00, 0x1a80, 0x1b00, 0x1b80,
-    0x1c00, 0x1c80, 0x1d00, 0x1d80, 0x1e00, 0x1e80, 0x1f00,
-    0x1f80, 0x2000, 0x2080, 0x2100, 0x2180, 0x2200, 0x2280,
-    0x2300, 0x2380, 0x2400, 0x2480, 0x2500, 0x2580, 0x2600,
-    0x2680, 0x2700, 0x2780, 0x2800, 0x2880, 0x2900, 0x2980,
-    0x2a00, 0x2a80, 0x2b00, 0x2b80, 0x2c00, 0x2c80, 0x2d00,
-    0x2d80, 0x2e00, 0x2e80, 0x2f00, 0x2f80, 0x3000, 0x3080,
-    0x3100, 0x3180, 0x3200, 0x3280, 0x3300, 0x3380, 0xe000,
-    0xe080, 0xe100, 0xe180, 0xe200, 0xe280, 0xe300, 0xe380,
-    0xe400, 0xe480, 0xe500, 0xe580, 0xe600, 0xe680, 0xe700,
-    0xe780, 0xe800, 0xe880, 0xe900, 0xe980, 0xea00, 0xea80,
-    0xeb00, 0xeb80, 0xec00, 0xec80, 0xed00, 0xed80, 0xee00,
-    0xee80, 0xef00, 0xef80, 0xf000, 0xf080, 0xf100, 0xf180,
-    0xf200, 0xf280, 0xf300, 0xf380, 0xf400, 0xf480, 0xf500,
-    0xf580, 0xf600, 0xf680, 0xf700, 0xf780, 0xf800, 0xf880,
-    0xf900, 0xf980, 0xfa00, 0xfa80, 0xfb00, 0xfb80, 0xfc00,
-    0xfc80, 0xfd00, 0xfd80, 0xfe00, 0xfe80, 0xff00, 0xff80, 0x0,
-    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc0, 0x250, 0x370,
-    0x530, 0x3040, 0x30a0, 0xff60  
-    };
-
-    /** Static compression window offsets */
-    final static int [] sOffsets = {
-        0x0000,  // for quoting single-byte mode tags
-        0x0080,  // Latin-1 Supplement
-        0x0100,  // Latin Extended-A
-        0x0300,  // Combining Diacritical Marks
-        0x2000,  // General Punctuation
-        0x2080,  // Curency Symbols
-        0x2100,  // Letterlike Symbols and Number Forms
-        0x3000   // CJK Symbols and Punctuation
-    };
-
-}
-
diff --git a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java b/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java
deleted file mode 100644
index 6789469..0000000
--- a/icu/icu4j/main/classes/core/src/com/ibm/icu/text/UnicodeDecompressor.java
+++ /dev/null
@@ -1,559 +0,0 @@
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-/*
- *******************************************************************************
- * Copyright (C) 1996-2016, International Business Machines Corporation and    *
- * others. All Rights Reserved.                                                *
- *******************************************************************************
- */
-
-package com.ibm.icu.text;
-
-/**
-* A decompression engine implementing the Standard Compression Scheme
-* for Unicode (SCSU) as outlined in <A
-* HREF="http://www.unicode.org/unicode/reports/tr6">Unicode Technical
-* Report #6</A>.
-*
-* <P><STRONG>USAGE</STRONG></P>
-*
-* <P>The static methods on <TT>UnicodeDecompressor</TT> may be used in a
-* straightforward manner to decompress simple strings:</P>
-*
-* <PRE>
-*  byte [] compressed = ... ; // get compressed bytes from somewhere
-*  String result = UnicodeDecompressor.decompress(compressed);
-* </PRE>
-*
-* <P>The static methods have a fairly large memory footprint.
-* For finer-grained control over memory usage, 
-* <TT>UnicodeDecompressor</TT> offers more powerful APIs allowing
-* iterative decompression:</P>
-*
-* <PRE>
-*  // Decompress an array "bytes" of length "len" using a buffer of 512 chars
-*  // to the Writer "out"
-*
-*  UnicodeDecompressor myDecompressor         = new UnicodeDecompressor();
-*  final static int    BUFSIZE                = 512;
-*  char []             charBuffer             = new char [ BUFSIZE ];
-*  int                 charsWritten           = 0;
-*  int []              bytesRead              = new int [1];
-*  int                 totalBytesDecompressed = 0;
-*  int                 totalCharsWritten      = 0;
-*
-*  do {
-*    // do the decompression
-*    charsWritten = myDecompressor.decompress(bytes, totalBytesDecompressed, 
-*                                             len, bytesRead,
-*                                             charBuffer, 0, BUFSIZE);
-*
-*    // do something with the current set of chars
-*    out.write(charBuffer, 0, charsWritten);
-*
-*    // update the no. of bytes decompressed
-*    totalBytesDecompressed += bytesRead[0];
-*
-*    // update the no. of chars written
-*    totalCharsWritten += charsWritten;
-*
-*  } while(totalBytesDecompressed &lt; len);
-*
-*  myDecompressor.reset(); // reuse decompressor
-* </PRE>
-*
-* <P>Decompression is performed according to the standard set forth in 
-* <A HREF="http://www.unicode.org/unicode/reports/tr6">Unicode Technical 
-* Report #6</A></P>
-*
-* @see UnicodeCompressor
-*
-* @author Stephen F. Booth
-* @stable ICU 2.4
-*/
-public final class UnicodeDecompressor implements SCSU
-{
-    //==========================
-    // Instance variables
-    //==========================
-    
-    /** Alias to current dynamic window */
-    private int       fCurrentWindow   = 0;
-
-    /** Dynamic compression window offsets */
-    private int []    fOffsets         = new int [ NUMWINDOWS ];
-
-    /** Current compression mode */
-    private int       fMode            = SINGLEBYTEMODE;
-
-    /** Size of our internal buffer */
-    private final static int BUFSIZE   = 3;
-
-    /** Internal buffer for saving state */
-    private byte []   fBuffer          = new byte [BUFSIZE];
-
-    /** Number of characters in our internal buffer */
-    private int       fBufferLength    = 0;
-    
-
-    /**
-     * Create a UnicodeDecompressor.
-     * Sets all windows to their default values.
-     * @see #reset
-     * @stable ICU 2.4
-     */
-    public UnicodeDecompressor(){
-        reset();              // initialize to defaults
-    }
-
-    /**
-     * Decompress a byte array into a String.
-     * @param buffer The byte array to decompress.
-     * @return A String containing the decompressed characters.
-     * @see #decompress(byte [], int, int)
-     * @stable ICU 2.4
-     */
-    public static String decompress(byte [] buffer){
-        char [] buf = decompress(buffer, 0, buffer.length);
-        return new String(buf);
-    }
-
-    /**
-     * Decompress a byte array into a Unicode character array.
-     * @param buffer The byte array to decompress.
-     * @param start The start of the byte run to decompress.
-     * @param limit The limit of the byte run to decompress.
-     * @return A character array containing the decompressed bytes.
-     * @see #decompress(byte [])
-     * @stable ICU 2.4
-     */
-    public static char [] decompress(byte [] buffer, int start, int limit) {
-        UnicodeDecompressor comp = new UnicodeDecompressor();
-    
-        // use a buffer we know will never overflow
-        // in the worst case, each byte will decompress
-        // to a surrogate pair (buffer must be at least 2 chars)
-        int len = Math.max(2, 2 * (limit - start));
-        char [] temp = new char [len];
-    
-        int charCount = comp.decompress(buffer, start, limit, null, 
-                        temp, 0, len);
-    
-        char [] result = new char [charCount];
-        System.arraycopy(temp, 0, result, 0, charCount);
-        return result;
-    }
-    
-    /**
-     * Decompress a byte array into a Unicode character array.
-     *
-     * This function will either completely fill the output buffer, 
-     * or consume the entire input.  
-     *
-     * @param byteBuffer The byte buffer to decompress.
-     * @param byteBufferStart The start of the byte run to decompress.
-     * @param byteBufferLimit The limit of the byte run to decompress.
-     * @param bytesRead A one-element array.  If not null, on return
-     * the number of bytes read from byteBuffer.
-     * @param charBuffer A buffer to receive the decompressed data. 
-     * This buffer must be at minimum two characters in size.
-     * @param charBufferStart The starting offset to which to write 
-     * decompressed data.
-     * @param charBufferLimit The limiting offset for writing 
-     * decompressed data.
-     * @return The number of Unicode characters written to charBuffer.
-     * @stable ICU 2.4
-     */
-    public int decompress(byte []    byteBuffer,
-              int        byteBufferStart,
-              int        byteBufferLimit,
-              int []     bytesRead,
-              char []    charBuffer,
-              int        charBufferStart,
-              int        charBufferLimit)
-    {
-    // the current position in the source byte buffer
-    int bytePos      = byteBufferStart;
-    
-    // the current position in the target char buffer
-    int ucPos        = charBufferStart;
-        
-        // the current byte from the source buffer
-    int aByte        = 0x00;
-
-
-    // charBuffer must be at least 2 chars in size
-    if(charBuffer.length < 2 || (charBufferLimit - charBufferStart) < 2)
-        throw new IllegalArgumentException("charBuffer.length < 2");
-    
-    // if our internal buffer isn't empty, flush its contents
-    // to the output buffer before doing any more decompression
-    if(fBufferLength > 0) {
-
-        int newBytes = 0;
-
-        // fill the buffer completely, to guarantee one full character
-        if(fBufferLength != BUFSIZE) {
-        newBytes = fBuffer.length - fBufferLength;
-
-        // verify there are newBytes bytes in byteBuffer
-        if(byteBufferLimit - byteBufferStart < newBytes)
-            newBytes = byteBufferLimit - byteBufferStart;
-
-        System.arraycopy(byteBuffer, byteBufferStart, 
-                 fBuffer, fBufferLength, newBytes);
-        }
-
-        // reset buffer length to 0 before recursive call
-        fBufferLength = 0;
-
-        // call self recursively to decompress the buffer
-        int count = decompress(fBuffer, 0, fBuffer.length, null,
-                   charBuffer, charBufferStart, 
-                   charBufferLimit);
-
-        // update the positions into the arrays
-        ucPos += count;
-        bytePos += newBytes;
-    }
-
-        // the main decompression loop
-    mainLoop:
-    while(bytePos < byteBufferLimit && ucPos < charBufferLimit) {
-        switch(fMode) {  
-        case SINGLEBYTEMODE:
-        // single-byte mode decompression loop
-        singleByteModeLoop:
-        while(bytePos < byteBufferLimit && ucPos < charBufferLimit) {
-        aByte = byteBuffer[bytePos++] & 0xFF;
-        switch(aByte) {
-            // All bytes from 0x80 through 0xFF are remapped
-            // to chars or surrogate pairs according to the
-            // currently active window
-        case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: 
-        case 0x85: case 0x86: case 0x87: case 0x88: case 0x89:
-        case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E:
-        case 0x8F: case 0x90: case 0x91: case 0x92: case 0x93:
-        case 0x94: case 0x95: case 0x96: case 0x97: case 0x98:
-        case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D:
-        case 0x9E: case 0x9F: case 0xA0: case 0xA1: case 0xA2:
-        case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7:
-        case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC:
-        case 0xAD: case 0xAE: case 0xAF: case 0xB0: case 0xB1:
-        case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6:
-        case 0xB7: case 0xB8: case 0xB9: case 0xBA: case 0xBB:
-        case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xC0:
-        case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5:
-        case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA:
-        case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF:
-        case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4:
-        case 0xD5: case 0xD6: case 0xD7: case 0xD8: case 0xD9:
-        case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE:
-        case 0xDF: case 0xE0: case 0xE1: case 0xE2: case 0xE3:
-        case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8:
-        case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED:
-        case 0xEE: case 0xEF: case 0xF0: case 0xF1: case 0xF2:
-        case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7:
-        case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC:
-        case 0xFD: case 0xFE: case 0xFF: 
-            // For offsets <= 0xFFFF, convert to a single char
-            // by adding the window's offset and subtracting
-            // the generic compression offset
-            if(fOffsets[ fCurrentWindow ] <= 0xFFFF) {
-            charBuffer[ucPos++] = (char) 
-                (aByte + fOffsets[ fCurrentWindow ] 
-                 - COMPRESSIONOFFSET);
-            }
-            // For offsets > 0x10000, convert to a surrogate pair by 
-            // normBase = window's offset - 0x10000
-            // high surr. = 0xD800 + (normBase >> 10)
-            // low  surr. = 0xDC00 + (normBase & 0x3FF) + (byte & 0x7F)
-            else {
-            // make sure there is enough room to write
-            // both characters 
-            // if not, save state and break out
-            if((ucPos + 1) >= charBufferLimit) {
-                --bytePos;
-                System.arraycopy(byteBuffer, bytePos,
-                         fBuffer, 0, 
-                         byteBufferLimit - bytePos);
-                fBufferLength = byteBufferLimit - bytePos;
-                bytePos += fBufferLength;
-                break mainLoop; 
-            }
-            
-            int normalizedBase = fOffsets[ fCurrentWindow ] 
-                - 0x10000;
-            charBuffer[ucPos++] = (char) 
-                (0xD800 + (normalizedBase >> 10));
-            charBuffer[ucPos++] = (char) 
-                (0xDC00 + (normalizedBase & 0x3FF)+(aByte & 0x7F));
-            }
-            break;
-
-            // bytes from 0x20 through 0x7F are treated as ASCII and
-            // are remapped to chars by padding the high byte
-            // (this is the same as quoting from static window 0)
-            // NUL (0x00), HT (0x09), CR (0x0A), LF (0x0D) 
-            // are treated as ASCII as well
-        case 0x00: case 0x09: case 0x0A: case 0x0D:
-        case 0x20: case 0x21: case 0x22: case 0x23: case 0x24:
-        case 0x25: case 0x26: case 0x27: case 0x28: case 0x29:
-        case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E:
-        case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33:
-        case 0x34: case 0x35: case 0x36: case 0x37: case 0x38:
-        case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D:
-        case 0x3E: case 0x3F: case 0x40: case 0x41: case 0x42:
-        case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
-        case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C:
-        case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51:
-        case 0x52: case 0x53: case 0x54: case 0x55: case 0x56:
-        case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B:
-        case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60:
-        case 0x61: case 0x62: case 0x63: case 0x64: case 0x65:
-        case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A:
-        case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
-        case 0x70: case 0x71: case 0x72: case 0x73: case 0x74:
-        case 0x75: case 0x76: case 0x77: case 0x78: case 0x79:
-        case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E:
-        case 0x7F: 
-            charBuffer[ucPos++] = (char) aByte;
-            break;
-
-            // quote unicode
-        case SQUOTEU:
-            // verify we have two bytes following tag
-            // if not, save state and break out
-            if( (bytePos + 1) >= byteBufferLimit ) {
-            --bytePos;
-            System.arraycopy(byteBuffer, bytePos,
-                     fBuffer, 0, 
-                     byteBufferLimit - bytePos);
-            fBufferLength = byteBufferLimit - bytePos;
-            bytePos += fBufferLength;
-            break mainLoop; 
-            }
-                
-            aByte = byteBuffer[bytePos++];
-            charBuffer[ucPos++] = (char)
-            (aByte << 8 | (byteBuffer[bytePos++] & 0xFF));
-            break;
-
-            // switch to Unicode mode
-        case SCHANGEU:
-            fMode = UNICODEMODE;
-            break singleByteModeLoop;
-            //break;
-
-            // handle all quote tags
-        case SQUOTE0: case SQUOTE1: case SQUOTE2: case SQUOTE3:
-        case SQUOTE4: case SQUOTE5: case SQUOTE6: case SQUOTE7:
-            // verify there is a byte following the tag
-            // if not, save state and break out
-            if(bytePos >= byteBufferLimit) {
-            --bytePos;
-            System.arraycopy(byteBuffer, bytePos,
-                     fBuffer, 0, 
-                     byteBufferLimit - bytePos);
-            fBufferLength = byteBufferLimit - bytePos;
-            bytePos += fBufferLength;
-            break mainLoop; 
-            }
-                
-            // if the byte is in the range 0x00 - 0x7F, use
-            // static window n otherwise, use dynamic window n
-            int dByte = byteBuffer[bytePos++] & 0xFF;
-            charBuffer[ucPos++] = (char) 
-            (dByte+ (dByte >= 0x00 && dByte < 0x80 
-                 ? sOffsets[aByte - SQUOTE0] 
-                 : (fOffsets[aByte - SQUOTE0] 
-                    - COMPRESSIONOFFSET))); 
-            break;
-
-            // handle all change tags
-        case SCHANGE0: case SCHANGE1: case SCHANGE2: case SCHANGE3:
-        case SCHANGE4: case SCHANGE5: case SCHANGE6: case SCHANGE7:
-            fCurrentWindow = aByte - SCHANGE0;
-            break;
-
-            // handle all define tags
-        case SDEFINE0: case SDEFINE1: case SDEFINE2: case SDEFINE3:
-        case SDEFINE4: case SDEFINE5: case SDEFINE6: case SDEFINE7:
-            // verify there is a byte following the tag
-            // if not, save state and break out
-            if(bytePos >= byteBufferLimit) {
-            --bytePos;
-            System.arraycopy(byteBuffer, bytePos,
-                     fBuffer, 0, 
-                     byteBufferLimit - bytePos);
-            fBufferLength = byteBufferLimit - bytePos;
-            bytePos += fBufferLength;
-            break mainLoop; 
-            }
-
-            fCurrentWindow = aByte - SDEFINE0;
-            fOffsets[fCurrentWindow] = 
-            sOffsetTable[byteBuffer[bytePos++] & 0xFF];
-            break;
-
-            // handle define extended tag
-        case SDEFINEX:
-            // verify we have two bytes following tag
-            // if not, save state and break out
-            if((bytePos + 1) >= byteBufferLimit ) {
-            --bytePos;
-            System.arraycopy(byteBuffer, bytePos,
-                     fBuffer, 0, 
-                     byteBufferLimit - bytePos);
-            fBufferLength = byteBufferLimit - bytePos;
-            bytePos += fBufferLength;
-            break mainLoop; 
-            }
-                
-            aByte = byteBuffer[bytePos++] & 0xFF;
-            fCurrentWindow = (aByte & 0xE0) >> 5;
-            fOffsets[fCurrentWindow] = 0x10000 + 
-            (0x80 * (((aByte & 0x1F) << 8) 
-                 | (byteBuffer[bytePos++] & 0xFF)));
-            break;
-                            
-            // reserved, shouldn't happen
-        case SRESERVED:
-            break;
-
-        } // end switch
-        } // end while
-        break;
-
-        case UNICODEMODE:
-        // unicode mode decompression loop
-        unicodeModeLoop:
-        while(bytePos < byteBufferLimit && ucPos < charBufferLimit) {
-        aByte = byteBuffer[bytePos++] & 0xFF;
-        switch(aByte) {
-            // handle all define tags
-        case UDEFINE0: case UDEFINE1: case UDEFINE2: case UDEFINE3:
-        case UDEFINE4: case UDEFINE5: case UDEFINE6: case UDEFINE7:
-            // verify there is a byte following tag
-            // if not, save state and break out
-            if(bytePos >= byteBufferLimit ) {
-            --bytePos;
-            System.arraycopy(byteBuffer, bytePos,
-                     fBuffer, 0, 
-                     byteBufferLimit - bytePos);
-            fBufferLength = byteBufferLimit - bytePos;
-            bytePos += fBufferLength;
-            break mainLoop; 
-            }
-                
-            fCurrentWindow = aByte - UDEFINE0;
-            fOffsets[fCurrentWindow] = 
-            sOffsetTable[byteBuffer[bytePos++] & 0xFF];
-            fMode = SINGLEBYTEMODE;
-            break unicodeModeLoop;
-            //break;
-
-            // handle define extended tag
-        case UDEFINEX:
-            // verify we have two bytes following tag
-            // if not, save state and break out
-            if((bytePos + 1) >= byteBufferLimit ) {
-            --bytePos;
-            System.arraycopy(byteBuffer, bytePos,
-                     fBuffer, 0, 
-                     byteBufferLimit - bytePos);
-            fBufferLength = byteBufferLimit - bytePos;
-            bytePos += fBufferLength;
-            break mainLoop; 
-            }
-            
-            aByte = byteBuffer[bytePos++] & 0xFF;
-            fCurrentWindow = (aByte & 0xE0) >> 5;
-            fOffsets[fCurrentWindow] = 0x10000 + 
-            (0x80 * (((aByte & 0x1F) << 8) 
-                 | (byteBuffer[bytePos++] & 0xFF)));
-            fMode = SINGLEBYTEMODE;
-            break unicodeModeLoop;
-            //break;
-
-            // handle all change tags
-        case UCHANGE0: case UCHANGE1: case UCHANGE2: case UCHANGE3:
-        case UCHANGE4: case UCHANGE5: case UCHANGE6: case UCHANGE7:
-            fCurrentWindow = aByte - UCHANGE0;
-            fMode = SINGLEBYTEMODE;
-            break unicodeModeLoop;
-            //break;
-
-            // quote unicode
-        case UQUOTEU:
-            // verify we have two bytes following tag
-            // if not, save state and break out
-            if(bytePos >= byteBufferLimit  - 1) {
-            --bytePos;
-            System.arraycopy(byteBuffer, bytePos,
-                     fBuffer, 0, 
-                     byteBufferLimit - bytePos);
-            fBufferLength = byteBufferLimit - bytePos;
-            bytePos += fBufferLength;
-            break mainLoop; 
-            }
-                
-            aByte = byteBuffer[bytePos++];
-            charBuffer[ucPos++] = (char) 
-            (aByte << 8 | (byteBuffer[bytePos++] & 0xFF));
-            break;
-
-        default:
-            // verify there is a byte following tag
-            // if not, save state and break out
-            if(bytePos >= byteBufferLimit ) {
-            --bytePos;
-            System.arraycopy(byteBuffer, bytePos,
-                     fBuffer, 0, 
-                     byteBufferLimit - bytePos);
-            fBufferLength = byteBufferLimit - bytePos;
-            bytePos += fBufferLength;
-            break mainLoop; 
-            }
-
-            charBuffer[ucPos++] = (char) 
-            (aByte << 8 | (byteBuffer[bytePos++] & 0xFF));
-            break;
-
-        } // end switch
-        } // end while
-        break;
-        
-        } // end switch( fMode )
-    } // end while
-
-        // fill in output parameter
-    if(bytesRead != null)
-        bytesRead [0] = (bytePos - byteBufferStart);
-
-        // return # of chars written
-    return (ucPos - charBufferStart);
-    }
-
-    /** 
-     * Reset the decompressor to its initial state. 
-     * @stable ICU 2.4
-     */
-    public void reset()
-    {
-        // reset dynamic windows
-        fOffsets[0] = 0x0080;    // Latin-1
-        fOffsets[1] = 0x00C0;    // Latin-1 Supplement + Latin Extended-A
-        fOffsets[2] = 0x0400;    // Cyrillic
-        fOffsets[3] = 0x0600;    // Arabic
-        fOffsets[4] = 0x0900;    // Devanagari
-        fOffsets[5] = 0x3040;    // Hiragana
-        fOffsets[6] = 0x30A0;    // Katakana
-        fOffsets[7] = 0xFF00;    // Fullwidth ASCII
-
-
-        fCurrentWindow  = 0;                // Make current window Latin-1
-        fMode           = SINGLEBYTEMODE;   // Always start in single-byte mode
-        fBufferLength   = 0;                // Empty buffer
-    }
-}
diff --git a/jni/DvbManager.cpp b/jni/DvbManager.cpp
index b344f80..8e51999 100644
--- a/jni/DvbManager.cpp
+++ b/jni/DvbManager.cpp
@@ -39,22 +39,17 @@
 
 DvbManager::DvbManager(JNIEnv *env, jobject)
         : mFeFd(-1),
-          mDemuxFd(-1),
           mDvrFd(-1),
           mPatFilterFd(-1),
           mDvbApiVersion(DVB_API_VERSION_UNDEFINED),
           mDeliverySystemType(-1),
           mFeHasLock(false),
           mHasPendingTune(false) {
-    (void) mDemuxFd; // suppress unused warning
-    jclass clazz = env->FindClass(
-        "com/android/tv/tuner/TunerHal");
-    mOpenDvbFrontEndMethodID = env->GetMethodID(
-        clazz, "openDvbFrontEndFd", "()I");
-    mOpenDvbDemuxMethodID = env->GetMethodID(
-        clazz, "openDvbDemuxFd", "()I");
-    mOpenDvbDvrMethodID = env->GetMethodID(
-        clazz, "openDvbDvrFd", "()I");
+  jclass clazz = env->FindClass("com/android/tv/tuner/TunerHal");
+  mOpenDvbFrontEndMethodID =
+      env->GetMethodID(clazz, "openDvbFrontEndFd", "()I");
+  mOpenDvbDemuxMethodID = env->GetMethodID(clazz, "openDvbDemuxFd", "()I");
+  mOpenDvbDvrMethodID = env->GetMethodID(clazz, "openDvbDvrFd", "()I");
 }
 
 DvbManager::~DvbManager() {
@@ -118,11 +113,13 @@
 
     if (mDvbApiVersion == DVB_API_VERSION5) {
         struct dtv_property deliverySystemProperty = {
-            .cmd = DTV_DELIVERY_SYSTEM, .u.data = SYS_ATSC
+            .cmd = DTV_DELIVERY_SYSTEM
         };
+        deliverySystemProperty.u.data = SYS_ATSC;
         struct dtv_property frequencyProperty = {
-            .cmd = DTV_FREQUENCY, .u.data = static_cast<__u32>(frequency)
+            .cmd = DTV_FREQUENCY
         };
+        frequencyProperty.u.data = static_cast<__u32>(frequency);
         struct dtv_property modulationProperty = { .cmd = DTV_MODULATION };
         if (strncmp(modulationStr, "QAM", 3) == 0) {
             modulationProperty.u.data = QAM_AUTO;
@@ -503,4 +500,4 @@
         }
     }
     return mDeliverySystemType;
-}
+}
\ No newline at end of file
diff --git a/jni/DvbManager.h b/jni/DvbManager.h
index 2252332..124fa94 100644
--- a/jni/DvbManager.h
+++ b/jni/DvbManager.h
@@ -36,32 +36,30 @@
     static const int DVB_API_VERSION5 = 5;
 
     static const int FILTER_TYPE_OTHER =
-            com_android_tv_tuner_TunerHal_FILTER_TYPE_OTHER;
+        com_android_tv_tuner_TunerHal_FILTER_TYPE_OTHER;
     static const int FILTER_TYPE_AUDIO =
-            com_android_tv_tuner_TunerHal_FILTER_TYPE_AUDIO;
+        com_android_tv_tuner_TunerHal_FILTER_TYPE_AUDIO;
     static const int FILTER_TYPE_VIDEO =
-            com_android_tv_tuner_TunerHal_FILTER_TYPE_VIDEO;
+        com_android_tv_tuner_TunerHal_FILTER_TYPE_VIDEO;
     static const int FILTER_TYPE_PCR =
-            com_android_tv_tuner_TunerHal_FILTER_TYPE_PCR;
+        com_android_tv_tuner_TunerHal_FILTER_TYPE_PCR;
 
     static const int DELIVERY_SYSTEM_UNDEFINED =
-            com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_UNDEFINED;
+        com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_UNDEFINED;
     static const int DELIVERY_SYSTEM_ATSC =
-            com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_ATSC;
+        com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_ATSC;
     static const int DELIVERY_SYSTEM_DVBC =
-            com_android_tv_tuner_TunerHal_DDELIVERY_SYSTEM_DVBC;
+        com_android_tv_tuner_TunerHal_DDELIVERY_SYSTEM_DVBC;
     static const int DELIVERY_SYSTEM_DVBS =
-            com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS;
+        com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS;
     static const int DELIVERY_SYSTEM_DVBS2 =
-            com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS2;
+        com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBS2;
     static const int DELIVERY_SYSTEM_DVBT =
-            com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT;
+        com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT;
     static const int DELIVERY_SYSTEM_DVBT2 =
-            com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT2;
-
+        com_android_tv_tuner_TunerHal_DELIVERY_SYSTEM_DVBT2;
 
     int mFeFd;
-    int mDemuxFd;
     int mDvrFd;
     int mPatFilterFd;
     int mDvbApiVersion;
diff --git a/jni/gen_jni.sh b/jni/gen_jni.sh
index 6acadee..c06b7b9 100755
--- a/jni/gen_jni.sh
+++ b/jni/gen_jni.sh
@@ -1,3 +1,18 @@
 #!/bin/bash
+#
+# 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.
+
 
 javah -jni -classpath ../../bin/classes:../../../../../../prebuilts/sdk/current/public/android.jar -o tunertvinput_jni.h com.android.tv.tuner.TunerHal
diff --git a/jni/tunertvinput_jni.cpp b/jni/tunertvinput_jni.cpp
index 9ad1514..368e2d5 100644
--- a/jni/tunertvinput_jni.cpp
+++ b/jni/tunertvinput_jni.cpp
@@ -37,14 +37,13 @@
  * Method:    nativeFinalize
  * Signature: (J)V
  */
-JNIEXPORT void JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeFinalize
-(JNIEnv *, jobject, jlong deviceId) {
-    std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
-    if (it != sDvbManagers.end()) {
-        delete it->second;
-        sDvbManagers.erase(it);
-    }
+JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeFinalize(
+    JNIEnv *, jobject, jlong deviceId) {
+  std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+  if (it != sDvbManagers.end()) {
+    delete it->second;
+    sDvbManagers.erase(it);
+  }
 }
 
 /*
@@ -52,20 +51,20 @@
  * Method:    nativeTune
  * Signature: (JILjava/lang/String;)Z
  */
-JNIEXPORT jboolean JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeTune
-(JNIEnv *env, jobject thiz, jlong deviceId, jint frequency, jstring modulation, jint timeout_ms) {
-    std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
-    DvbManager *dvbManager;
-    if (it == sDvbManagers.end()) {
-        dvbManager = new DvbManager(env, thiz);
-        sDvbManagers.insert(std::pair<jlong, DvbManager *>(deviceId, dvbManager));
-    } else {
-        dvbManager = it->second;
-    }
-    int res = dvbManager->tune(env, thiz,
-            frequency, env->GetStringUTFChars(modulation, 0), timeout_ms);
-    return (res == 0);
+JNIEXPORT jboolean JNICALL Java_com_android_tv_tuner_TunerHal_nativeTune(
+    JNIEnv *env, jobject thiz, jlong deviceId, jint frequency,
+    jstring modulation, jint timeout_ms) {
+  std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+  DvbManager *dvbManager;
+  if (it == sDvbManagers.end()) {
+    dvbManager = new DvbManager(env, thiz);
+    sDvbManagers.insert(std::pair<jlong, DvbManager *>(deviceId, dvbManager));
+  } else {
+    dvbManager = it->second;
+  }
+  int res = dvbManager->tune(env, thiz, frequency,
+                             env->GetStringUTFChars(modulation, 0), timeout_ms);
+  return (res == 0);
 }
 
 /*
@@ -73,12 +72,13 @@
  * Method:    nativeCloseAllPidFilters
  * Signature: (J)V
  */
-JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeCloseAllPidFilters
-  (JNIEnv *, jobject, jlong deviceId) {
-    std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
-    if (it != sDvbManagers.end()) {
-        it->second->closeAllDvbPidFilter();
-    }
+JNIEXPORT void JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeCloseAllPidFilters(JNIEnv *, jobject,
+                                                            jlong deviceId) {
+  std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+  if (it != sDvbManagers.end()) {
+    it->second->closeAllDvbPidFilter();
+  }
 }
 
 /*
@@ -86,13 +86,12 @@
  * Method:    nativeStopTune
  * Signature: (J)V
  */
-JNIEXPORT void JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeStopTune
-(JNIEnv *, jobject, jlong deviceId) {
-    std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
-    if (it != sDvbManagers.end()) {
-        it->second->stopTune();
-    }
+JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeStopTune(
+    JNIEnv *, jobject, jlong deviceId) {
+  std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+  if (it != sDvbManagers.end()) {
+    it->second->stopTune();
+  }
 }
 
 /*
@@ -100,13 +99,12 @@
  * Method:    nativeAddPidFilter
  * Signature: (JII)V
  */
-JNIEXPORT void JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeAddPidFilter
-(JNIEnv *env, jobject thiz, jlong deviceId, jint pid, jint filterType) {
-    std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
-    if (it != sDvbManagers.end()) {
-        it->second->startTsPidFilter(env, thiz, pid, filterType);
-    }
+JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeAddPidFilter(
+    JNIEnv *env, jobject thiz, jlong deviceId, jint pid, jint filterType) {
+  std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+  if (it != sDvbManagers.end()) {
+    it->second->startTsPidFilter(env, thiz, pid, filterType);
+  }
 }
 
 /*
@@ -114,32 +112,34 @@
  * Method:    nativeWriteInBuffer
  * Signature: (J[BI)I
  */
-JNIEXPORT jint JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeWriteInBuffer
-(JNIEnv *env, jobject thiz, jlong deviceId, jbyteArray javaBuffer, jint javaBufferSize) {
-    uint8_t tsBuffer[TS_PAYLOAD_SIZE];
-    std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
-    if (it == sDvbManagers.end()) {
-        return -1;
-    }
-    DvbManager *dvbManager = it->second;
+JNIEXPORT jint JNICALL Java_com_android_tv_tuner_TunerHal_nativeWriteInBuffer(
+    JNIEnv *env, jobject thiz, jlong deviceId, jbyteArray javaBuffer,
+    jint javaBufferSize) {
+  uint8_t tsBuffer[TS_PAYLOAD_SIZE];
+  std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+  if (it == sDvbManagers.end()) {
+    return -1;
+  }
+  DvbManager *dvbManager = it->second;
 
-    // Always read multiple of TS_PACKET_SIZE
-    javaBufferSize = (javaBufferSize / TS_PACKET_SIZE) * TS_PACKET_SIZE;
-    int readBufferSize = (javaBufferSize < TS_PAYLOAD_SIZE) ? javaBufferSize : TS_PAYLOAD_SIZE;
+  // Always read multiple of TS_PACKET_SIZE
+  javaBufferSize = (javaBufferSize / TS_PACKET_SIZE) * TS_PACKET_SIZE;
+  int readBufferSize =
+      (javaBufferSize < TS_PAYLOAD_SIZE) ? javaBufferSize : TS_PAYLOAD_SIZE;
 
-    int dataSize = dvbManager->readTsStream(env, thiz, tsBuffer, readBufferSize, READ_TIMEOUT_MS);
-    if (dataSize == 0) {
-        ALOGD("No data to read DVR");
-        return 0;
-    } else if (dataSize < 0) {
-        return -1;
-    }
+  int dataSize = dvbManager->readTsStream(env, thiz, tsBuffer, readBufferSize,
+                                          READ_TIMEOUT_MS);
+  if (dataSize == 0) {
+    ALOGD("No data to read DVR");
+    return 0;
+  } else if (dataSize < 0) {
+    return -1;
+  }
 
-    sTotalBytesFetched += dataSize;
+  sTotalBytesFetched += dataSize;
 
-    env->SetByteArrayRegion(javaBuffer, 0, dataSize, (jbyte *) tsBuffer);
-    return dataSize;
+  env->SetByteArrayRegion(javaBuffer, 0, dataSize, (jbyte *)tsBuffer);
+  return dataSize;
 }
 
 /*
@@ -148,12 +148,12 @@
  * Signature: (JZ)V
  */
 JNIEXPORT void JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeSetHasPendingTune
-(JNIEnv *, jobject, jlong deviceId, jboolean hasPendingTune) {
-    std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
-    if (it != sDvbManagers.end()) {
-        it->second->setHasPendingTune(hasPendingTune);
-    }
+Java_com_android_tv_tuner_TunerHal_nativeSetHasPendingTune(
+    JNIEnv *, jobject, jlong deviceId, jboolean hasPendingTune) {
+  std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+  if (it != sDvbManagers.end()) {
+    it->second->setHasPendingTune(hasPendingTune);
+  }
 }
 
 /*
@@ -162,14 +162,15 @@
  * Signature: (J)I
  */
 JNIEXPORT int JNICALL
-Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType
-(JNIEnv *env, jobject thiz, jlong deviceId) {
-    std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
-    if (it != sDvbManagers.end()) {
-        return it->second->getDeliverySystemType(env, thiz);
-    } else {
-        DvbManager *dvbManager = new DvbManager(env, thiz);
-        sDvbManagers.insert(std::pair<jlong, DvbManager *>(deviceId, dvbManager));
-        return dvbManager->getDeliverySystemType(env, thiz);
-    }
-}
+Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType(JNIEnv *env,
+                                                               jobject thiz,
+                                                               jlong deviceId) {
+  std::map<jlong, DvbManager *>::iterator it = sDvbManagers.find(deviceId);
+  if (it != sDvbManagers.end()) {
+    return it->second->getDeliverySystemType(env, thiz);
+  } else {
+    DvbManager *dvbManager = new DvbManager(env, thiz);
+    sDvbManagers.insert(std::pair<jlong, DvbManager *>(deviceId, dvbManager));
+    return dvbManager->getDeliverySystemType(env, thiz);
+  }
+}
\ No newline at end of file
diff --git a/jni/tunertvinput_jni.h b/jni/tunertvinput_jni.h
index fcd64d5..d299c30 100644
--- a/jni/tunertvinput_jni.h
+++ b/jni/tunertvinput_jni.h
@@ -44,64 +44,67 @@
  * Method:    nativeFinalize
  * Signature: (J)V
  */
-JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeFinalize
-  (JNIEnv *, jobject, jlong);
+JNIEXPORT void JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeFinalize(JNIEnv *, jobject, jlong);
 
 /*
  * Class:     com_android_tv_tuner_TunerHal
  * Method:    nativeTune
  * Signature: (JILjava/lang/String;I)Z
  */
-JNIEXPORT jboolean JNICALL Java_com_android_tv_tuner_TunerHal_nativeTune
-  (JNIEnv *, jobject, jlong, jint, jstring, jint);
+JNIEXPORT jboolean JNICALL Java_com_android_tv_tuner_TunerHal_nativeTune(
+    JNIEnv *, jobject, jlong, jint, jstring, jint);
 
 /*
  * Class:     com_android_tv_tuner_TunerHal
  * Method:    nativeAddPidFilter
  * Signature: (JII)V
  */
-JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeAddPidFilter
-  (JNIEnv *, jobject, jlong, jint, jint);
+JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeAddPidFilter(
+    JNIEnv *, jobject, jlong, jint, jint);
 
 /*
  * Class:     com_android_tv_tuner_TunerHal
  * Method:    nativeCloseAllPidFilters
  * Signature: (J)V
  */
-JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeCloseAllPidFilters
-  (JNIEnv *, jobject, jlong);
+JNIEXPORT void JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeCloseAllPidFilters(JNIEnv *, jobject,
+                                                            jlong);
 
 /*
  * Class:     com_android_tv_tuner_TunerHal
  * Method:    nativeStopTune
  * Signature: (J)V
  */
-JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeStopTune
-  (JNIEnv *, jobject, jlong);
+JNIEXPORT void JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeStopTune(JNIEnv *, jobject, jlong);
 
 /*
  * Class:     com_android_tv_tuner_TunerHal
  * Method:    nativeWriteInBuffer
  * Signature: (J[BI)I
  */
-JNIEXPORT jint JNICALL Java_com_android_tv_tuner_TunerHal_nativeWriteInBuffer
-  (JNIEnv *, jobject, jlong, jbyteArray, jint);
+JNIEXPORT jint JNICALL Java_com_android_tv_tuner_TunerHal_nativeWriteInBuffer(
+    JNIEnv *, jobject, jlong, jbyteArray, jint);
 
 /*
  * Class:     com_android_tv_tuner_TunerHal
  * Method:    nativeSetHasPendingTune
  * Signature: (JZ)V
  */
-JNIEXPORT void JNICALL Java_com_android_tv_tuner_TunerHal_nativeSetHasPendingTune
-  (JNIEnv *, jobject, jlong, jboolean);
+JNIEXPORT void JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeSetHasPendingTune(JNIEnv *, jobject,
+                                                           jlong, jboolean);
 
 /*
  * Class:     com_android_tv_tuner_TunerHal
  * Method:    nativeGetDeliverySystemType
  * Signature: (J)I
  */
-JNIEXPORT int JNICALL Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType
-  (JNIEnv *, jobject, jlong);
+JNIEXPORT int JNICALL
+Java_com_android_tv_tuner_TunerHal_nativeGetDeliverySystemType(JNIEnv *,
+                                                               jobject, jlong);
 
 #ifdef __cplusplus
 }
diff --git a/libs/exoplayer-core-2-SNAPHOT-20180114.aar b/libs/exoplayer-core-2-SNAPHOT-20180114.aar
new file mode 100644
index 0000000..90af2e6
--- /dev/null
+++ b/libs/exoplayer-core-2-SNAPHOT-20180114.aar
Binary files differ
diff --git a/libs/exoplayer-r1.5.16.aar b/libs/exoplayer-r1.5.16.aar
new file mode 100644
index 0000000..364104f
--- /dev/null
+++ b/libs/exoplayer-r1.5.16.aar
Binary files differ
diff --git a/libs/exoplayer.jar b/libs/exoplayer.jar
deleted file mode 100644
index 43aea97..0000000
--- a/libs/exoplayer.jar
+++ /dev/null
Binary files differ
diff --git a/libs/exoplayer_v2.jar b/libs/exoplayer_v2.jar
deleted file mode 100644
index 6cfcde9..0000000
--- a/libs/exoplayer_v2.jar
+++ /dev/null
Binary files differ
diff --git a/libs/exoplayer_v2_ext_ffmpeg.jar b/libs/exoplayer_v2_ext_ffmpeg.jar
deleted file mode 100644
index 6511726..0000000
--- a/libs/exoplayer_v2_ext_ffmpeg.jar
+++ /dev/null
Binary files differ
diff --git a/partner_support/Android.mk b/partner_support/Android.mk
new file mode 100644
index 0000000..8306921
--- /dev/null
+++ b/partner_support/Android.mk
@@ -0,0 +1,23 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Include all java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := live-channels-partner-support
+LOCAL_MODULE_CLASS := STATIC_JAVA_LIBRARIES
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := system_current
+LOCAL_MIN_SDK_VERSION := 23
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-annotations
+
+include $(LOCAL_PATH)/buildconfig.mk
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/partner_support/AndroidManifest.xml b/partner_support/AndroidManifest.xml
new file mode 100644
index 0000000..5a45f0d
--- /dev/null
+++ b/partner_support/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.tv.partner.support"
+    android:versionCode="1">
+  <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="21"/>
+  <application />
+</manifest>
diff --git a/partner_support/BuildConfig.java.in b/partner_support/BuildConfig.java.in
new file mode 100644
index 0000000..21d5901
--- /dev/null
+++ b/partner_support/BuildConfig.java.in
@@ -0,0 +1,8 @@
+/* This file is auto generated. Do not modify. */
+package com.google.android.tv.partner.support;
+
+public final class BuildConfig {
+    public static final boolean DEBUG = %DEBUG%;
+    public static final boolean ENG = %ENG%;
+    private BuildConfig() {}
+}
\ No newline at end of file
diff --git a/partner_support/README.md b/partner_support/README.md
new file mode 100644
index 0000000..13c9a76
--- /dev/null
+++ b/partner_support/README.md
@@ -0,0 +1,3 @@
+# Code and samples for Partner integration with Live Channels
+
+This code will only work for partners that are working with the Live Channels team on integration.
\ No newline at end of file
diff --git a/partner_support/buildconfig.mk b/partner_support/buildconfig.mk
new file mode 100644
index 0000000..cece7f2
--- /dev/null
+++ b/partner_support/buildconfig.mk
@@ -0,0 +1,39 @@
+#
+# Copyright (C) 2015 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.
+#
+
+# Emulate gradles BuildConfig.java
+
+ifeq "$(TARGET_BUILD_VARIANT)" "eng"
+   BC_DEBUG_STATUS := true
+else ifeq "$(TARGET_BUILD_VARIANT)" "userdebug"
+   BC_DEBUG_STATUS := true
+else
+   BC_DEBUG_STATUS := false
+endif
+
+ifeq "$(TARGET_BUILD_VARIANT)" "eng"
+   BC_ENG_STATUS := true
+else
+   BC_ENG_STATUS := false
+endif
+
+gen := $(local-generated-sources-dir)/$(TARGET_BUILD_VARIANT)/BuildConfig.java
+$(gen): PRIVATE_CUSTOM_TOOL = sed -e \
+        's/%DEBUG%/$(BC_DEBUG_STATUS)/;s/%ENG%/$(BC_ENG_STATUS)/' \
+        $< > $@
+$(gen) : $(LOCAL_PATH)/BuildConfig.java.in
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(gen)
\ No newline at end of file
diff --git a/partner_support/g3doc/CloudEpgForPartners.md b/partner_support/g3doc/CloudEpgForPartners.md
new file mode 100644
index 0000000..bec6b50
--- /dev/null
+++ b/partner_support/g3doc/CloudEpgForPartners.md
@@ -0,0 +1,112 @@
+# 3rd party instructions for using Cloud EPG feature of Live Channels
+
+Partners can ask Live Channels to retrieve EPG data for their TV Input Service
+using live channels
+
+## Prerequisites
+
+*   Updated agreement with Google
+*   Oreo or patched Nougat
+
+## Nougat
+
+To use cloud epg with Nougat you will need the following changes.
+
+### Patch TVProvider
+
+To run in Nougat you must cherry pick [change
+455319](https://android-review.googlesource.com/c/platform/packages/providers/TvProvider/+/455319)
+to TV Provider.
+
+### Customisation
+
+Indicate TvProvider is patched by including the following in their TV
+customization resource
+
+```
+<bool name="tvprovider_allows_system_inserts_to_program_table">true</bool>
+```
+
+See https://source.android.com/devices/tv/customize-tv-app
+
+## **Input Setup**
+
+During the input setup activity, the TIS will query the content provider for
+lineups in a given postal code. The TIS then inserts a row to the inputs table
+with input_id and lineup_id
+
+On completion of the activity the TIS sets the extra data in the result to
+
+*   `com.android.tv.extra.USE_CLOUD_EPG = true`
+*   `TvInputInfo.EXTRA_INPUT_ID` with their input_id
+
+This is used to tell Live Channels to immediately start the EPG fetch for that
+input.
+
+### Sample Input Setup code.
+
+A complete sample is at
+../third_party/samples/src/com/example/partnersupportsampletvinput
+
+#### query lineup
+
+```java
+ private AsyncTask<Void, Void, List<Lineup>> createFetchLineupsTask() {
+        return new AsyncTask<Void, Void, List<Lineup>>() {
+            @Override
+            protected List<Lineup> doInBackground(Void... params) {
+                ContentResolver cr = getActivity().getContentResolver();
+
+                List<Lineup> results = new ArrayList<>();
+                Cursor cursor =
+                        cr.query(
+                                Uri.parse(
+                                        "content://com.android.tv.data.epg/lineups/postal_code/"
+                                                + ZIP),
+                                null,
+                                null,
+                                null,
+                                null);
+
+                while (cursor.moveToNext()) {
+                    String id = cursor.getString(0);
+                    String name = cursor.getString(1);
+                    String channels = cursor.getString(2);
+                    results.add(new Lineup(id, name, channels));
+                }
+
+                return results;
+            }
+
+            @Override
+            protected void onPostExecute(List<Lineup> lineups) {
+                showLineups(lineups);
+            }
+        };
+    }
+```
+
+#### Insert cloud_epg_input
+
+```java
+ContentValues values = new ContentValues();
+values.put(EpgContract.EpgInputs.COLUMN_INPUT_ID, SampleTvInputService.INPUT_ID);
+values.put(EpgContract.EpgInputs.COLUMN_LINEUP_ID, lineup.getId());
+ContentResolver contentResolver = getActivity().getContentResolver();
+EpgInput epgInput = EpgInputs.queryEpgInput(contentResolver, SampleTvInputService.INPUT_ID);
+if (epgInput == null) {
+    contentResolver.insert(EpgContract.EpgInputs.CONTENT_URI, values);
+} else {
+    values.put(EpgContract.EpgInputs.COLUMN_ID, epgInput.getId());
+    EpgInputs.update(contentResolver, EpgInput.createEpgChannel(values));
+}
+```
+
+#### Return use_cloud_epg
+
+```java
+Intent data = new Intent();
+data.putExtra(TvInputInfo.EXTRA_INPUT_ID, inputId);
+data.putExtra(com.android.tv.extra.USE_CLOUD_EPG, true);
+setResult(Activity.RESULT_OK, data);
+```
diff --git a/partner_support/res/values/strings.xml b/partner_support/res/values/strings.xml
new file mode 100644
index 0000000..aad2d44
--- /dev/null
+++ b/partner_support/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+</resources>
\ No newline at end of file
diff --git a/partner_support/sample_customization/Android.mk b/partner_support/sample_customization/Android.mk
new file mode 100644
index 0000000..11ed13b
--- /dev/null
+++ b/partner_support/sample_customization/Android.mk
@@ -0,0 +1,18 @@
+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/sample_customization/AndroidManifest.xml b/partner_support/sample_customization/AndroidManifest.xml
new file mode 100644
index 0000000..f1edad3
--- /dev/null
+++ b/partner_support/sample_customization/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.example.tvcustomization">
+    <!-- Customization package must have this permission to customize TV apps. -->
+    <uses-permission android:name="com.android.tv.permission.CUSTOMIZE_TV_APP"/>
+
+    <!-- Enable leanback library support. -->
+    <uses-feature android:name="android.software.leanback" android:required="true" />
+    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
+
+    <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/>
+
+    <application android:label="Partner Customization"
+            android:theme="@android:style/Theme.Holo.Light.NoActionBar"
+        android:icon="@mipmap/ic_launcher"
+        >
+
+    </application>
+</manifest>
diff --git a/partner_support/sample_customization/res/mipmap-hdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..26add7f
--- /dev/null
+++ b/partner_support/sample_customization/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/mipmap-mdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..1ac20db
--- /dev/null
+++ b/partner_support/sample_customization/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/mipmap-xhdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..f6cf645
--- /dev/null
+++ b/partner_support/sample_customization/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/mipmap-xxhdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..72a250d
--- /dev/null
+++ b/partner_support/sample_customization/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/mipmap-xxxhdpi/ic_launcher.png b/partner_support/sample_customization/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..648001f
--- /dev/null
+++ b/partner_support/sample_customization/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/sample_customization/res/values/bools.xml b/partner_support/sample_customization/res/values/bools.xml
new file mode 100644
index 0000000..54fbe07
--- /dev/null
+++ b/partner_support/sample_customization/res/values/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <bool name="tvprovider_allows_system_inserts_to_program_table">true</bool>
+</resources>
\ No newline at end of file
diff --git a/partner_support/samples/Android.mk b/partner_support/samples/Android.mk
new file mode 100644
index 0000000..922a5b5
--- /dev/null
+++ b/partner_support/samples/Android.mk
@@ -0,0 +1,33 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Include all java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := PartnerSupportSampleTvInput
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-annotations \
+    live-channels-partner-support
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    android-support-compat \
+    android-support-core-ui \
+    android-support-v7-recyclerview \
+    android-support-v17-leanback \
+    android-support-tv-provider
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_PROGUARD_ENABLED := disabled
+# Overlay view related functionality requires system APIs.
+LOCAL_SDK_VERSION := system_current
+LOCAL_MIN_SDK_VERSION := 23  # M
+
+# Required for com.android.tv.permission.RECEIVE_INPUT_EVENT
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+include $(BUILD_PACKAGE)
diff --git a/partner_support/samples/AndroidManifest.xml b/partner_support/samples/AndroidManifest.xml
new file mode 100644
index 0000000..b9e086c
--- /dev/null
+++ b/partner_support/samples/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          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" />
+    <!-- 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="26" 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"
+            android:icon="@mipmap/ic_launcher"
+            android:theme="@android:style/Theme.Holo.Light.NoActionBar" >
+        <activity android:name=".SampleTvInputSetupActivity"
+                android:theme="@style/Theme.Leanback.GuidedStep">
+            <intent-filter>
+                <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">
+            <intent-filter>
+                <action android:name="android.media.tv.TvInputService" />
+            </intent-filter>
+            <meta-data android:name="android.media.tv.input"
+                android:resource="@xml/sampletvinputservice" />
+        </service>
+    </application>
+</manifest>
diff --git a/partner_support/samples/res/mipmap-hdpi/ic_launcher.png b/partner_support/samples/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..a827add
--- /dev/null
+++ b/partner_support/samples/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/mipmap-mdpi/ic_launcher.png b/partner_support/samples/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..d7d36f2
--- /dev/null
+++ b/partner_support/samples/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/mipmap-xhdpi/ic_launcher.png b/partner_support/samples/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..210bfcf
--- /dev/null
+++ b/partner_support/samples/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/mipmap-xxhdpi/ic_launcher.png b/partner_support/samples/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..59a090c
--- /dev/null
+++ b/partner_support/samples/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/mipmap-xxxhdpi/ic_launcher.png b/partner_support/samples/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..388b6eb
--- /dev/null
+++ b/partner_support/samples/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/partner_support/samples/res/values/strings.xml b/partner_support/samples/res/values/strings.xml
new file mode 100644
index 0000000..c6f0ac9
--- /dev/null
+++ b/partner_support/samples/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <string name="partner_support_sample_tv_input" translatable="false">Partner Support Sample Tv Input</string>
+</resources>
\ No newline at end of file
diff --git a/partner_support/samples/res/xml/sampletvinputservice.xml b/partner_support/samples/res/xml/sampletvinputservice.xml
new file mode 100644
index 0000000..e5211db
--- /dev/null
+++ b/partner_support/samples/res/xml/sampletvinputservice.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+    android:setupActivity="com.example.partnersupportsampletvinput.SampleTvInputSetupActivity" />
\ No newline at end of file
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java
new file mode 100644
index 0000000..35f4b69
--- /dev/null
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/ChannelScanFragment.java
@@ -0,0 +1,265 @@
+/*
+ * 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.example.partnersupportsampletvinput;
+
+import android.app.FragmentManager;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.media.tv.Channel;
+import android.support.media.tv.TvContractCompat;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/** Channel Scan Fragment shows detected channels */
+public class ChannelScanFragment extends GuidedStepFragment {
+    public static final long ACTION_ID_SCAN_RESULTS = 1;
+    public static final String KEY_POSTCODE = "postcode";
+    public static final String KEY_CHANNEL_NUMBERS = "channel numbers";
+    private GuidedAction mNextAction;
+    private List<Channel> mChannels = new ArrayList<>();
+    private Bundle mSavedInstanceState;
+
+    private AsyncTask<Void, Void, List<Channel>> mChannelScanTask;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSavedInstanceState = savedInstanceState;
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View v = super.onCreateView(inflater, container, savedInstanceState);
+        mChannelScanTask =
+                new AsyncTask<Void, Void, List<Channel>>() {
+                    @Override
+                    protected List<Channel> doInBackground(Void... voids) {
+                        return scanChannel();
+                    }
+
+                    @Override
+                    protected void onPostExecute(List<Channel> result) {
+                        mChannels = result;
+                        mNextAction.setFocusable(true);
+                        mNextAction.setEnabled(true);
+                        int position = findButtonActionPositionById(GuidedAction.ACTION_ID_NEXT);
+                        if (position >= 0) {
+                            notifyButtonActionChanged(position);
+                            View buttonView = getButtonActionItemView(position);
+                            buttonView.setFocusable(true);
+                            buttonView.requestFocus();
+                        }
+                    }
+                };
+        mChannelScanTask.execute();
+        return v;
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mNextAction = null;
+        if (mChannelScanTask != null) {
+            mChannelScanTask.cancel(true);
+            mChannelScanTask = null;
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // reset actions
+        List<GuidedAction> actions = new ArrayList<>();
+        onCreateActions(actions, mSavedInstanceState);
+        setActions(actions);
+        actions = new ArrayList<>();
+        onCreateButtonActions(actions, mSavedInstanceState);
+        setButtonActions(actions);
+    }
+
+    @NonNull
+    @Override
+    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+        return new Guidance("Channel Scan", "Fake scanning for channels", null, null);
+    }
+
+    @Override
+    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+        actions.add(
+                new GuidedAction.Builder(getActivity())
+                        .id(ACTION_ID_SCAN_RESULTS)
+                        .title("Channels Found:")
+                        .description("")
+                        .multilineDescription(true)
+                        .focusable(false)
+                        .infoOnly(true)
+                        .build());
+    }
+
+    @Override
+    public void onCreateButtonActions(
+            @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+        mNextAction =
+                new GuidedAction.Builder(getActivity())
+                        .id(GuidedAction.ACTION_ID_NEXT)
+                        .title("NEXT")
+                        .focusable(false)
+                        .enabled(false)
+                        .build();
+        actions.add(mNextAction);
+        actions.add(
+                new GuidedAction.Builder(getActivity())
+                        .id(GuidedAction.ACTION_ID_CANCEL)
+                        .title("CANCEL")
+                        .build());
+    }
+
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+        if (action == null) {
+            return;
+        }
+        FragmentManager fm = getFragmentManager();
+        switch ((int) action.getId()) {
+            case (int) GuidedAction.ACTION_ID_NEXT:
+                LineupSelectionFragment fragment = new LineupSelectionFragment();
+                Bundle args = new Bundle();
+                args.putString(KEY_POSTCODE, getArguments().getString(KEY_POSTCODE));
+                ArrayList<String> channelNumbers = new ArrayList<>();
+                for (Channel channel : mChannels) {
+                    if (channel != null) {
+                        channelNumbers.add(channel.getDisplayNumber());
+                    }
+                }
+                args.putStringArrayList(KEY_CHANNEL_NUMBERS, channelNumbers);
+                fragment.setArguments(args);
+                GuidedStepFragment.add(getFragmentManager(), fragment);
+                break;
+            case (int) GuidedAction.ACTION_ID_CANCEL:
+                fm.popBackStack();
+                break;
+            default:
+        }
+    }
+
+    private List<Channel> scanChannel() {
+        List<Channel> channels = new ArrayList<>();
+        channels.add(createFakeChannel("KQED", "9.1", 1));
+        channels.add(createFakeChannel("BBC", "2-2", 2));
+        channels.add(createFakeChannel("What\'s on", "1.1", 3));
+        channels.add(createFakeChannel("FOX", "7.1", 4));
+        channels.add(createFakeChannel("Play Movie", "1.1", 5));
+        channels.add(createFakeChannel("PBS", "9.2", 6));
+        channels.add(createFakeChannel("ApiTestChannel", "A.1", 7));
+        updateChannels(channels);
+
+        return channels;
+    }
+
+    private void updateChannels(List<Channel> channels) {
+        HashMap<Integer, Channel> networkId2newChannel = new HashMap<>();
+        for (Channel channel : channels) {
+            networkId2newChannel.put(channel.getOriginalNetworkId(), channel);
+        }
+
+        ContentResolver cr = getActivity().getContentResolver();
+        Cursor cursor =
+                cr.query(
+                        TvContractCompat.buildChannelsUriForInput(SampleTvInputService.INPUT_ID),
+                        Channel.PROJECTION,
+                        null,
+                        null,
+                        null,
+                        null);
+        ArrayList<Channel> existingChannels = new ArrayList<>();
+        while (cursor.moveToNext()) {
+            existingChannels.add(Channel.fromCursor(cursor));
+        }
+        Iterator<Channel> i = existingChannels.iterator();
+        while (i.hasNext()) {
+            Channel existingChannel = i.next();
+            if (networkId2newChannel.containsKey(existingChannel.getOriginalNetworkId())) {
+                i.remove();
+                Channel newChannel =
+                        networkId2newChannel.remove(existingChannel.getOriginalNetworkId());
+                cr.update(
+                        TvContractCompat.buildChannelUri(existingChannel.getId()),
+                        newChannel.toContentValues(),
+                        null,
+                        null);
+            }
+        }
+        for (Channel newChannel : networkId2newChannel.values()) {
+            cr.insert(TvContractCompat.Channels.CONTENT_URI, newChannel.toContentValues());
+        }
+        for (Channel existingChannel : existingChannels) {
+            cr.delete(TvContractCompat.buildChannelUri(existingChannel.getId()), null, null);
+        }
+    }
+
+    private Channel createFakeChannel(String name, String number, int networkId) {
+        Channel channel =
+                new Channel.Builder()
+                        .setDisplayName(name)
+                        .setDisplayNumber(number)
+                        .setInputId(SampleTvInputService.INPUT_ID)
+                        .setOriginalNetworkId(networkId)
+                        .build();
+        onChannelDetected(channel);
+        SystemClock.sleep(1000);
+
+        return channel;
+    }
+
+    private void onChannelDetected(Channel channel) {
+        getActivity()
+                .runOnUiThread(
+                        new Runnable() {
+                            @Override
+                            public void run() {
+                                List<GuidedAction> actions = getActions();
+                                actions.add(
+                                        new GuidedAction.Builder(getActivity())
+                                                .id(ACTION_ID_SCAN_RESULTS)
+                                                .title(
+                                                        channel.getDisplayNumber()
+                                                                + " "
+                                                                + channel.getDisplayName())
+                                                .focusable(false)
+                                                .build());
+                                if (actions.size() >= 7) {
+                                    actions.remove(1);
+                                }
+                                setActions(actions);
+                            }
+                        });
+    }
+}
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/LineupSelectionFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/LineupSelectionFragment.java
new file mode 100644
index 0000000..8c3ca77
--- /dev/null
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/LineupSelectionFragment.java
@@ -0,0 +1,278 @@
+/*
+ * 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.example.partnersupportsampletvinput;
+
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.google.android.tv.partner.support.EpgContract;
+import com.google.android.tv.partner.support.EpgInput;
+import com.google.android.tv.partner.support.EpgInputs;
+import com.google.android.tv.partner.support.Lineup;
+import com.google.android.tv.partner.support.Lineups;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/** Lineup Selection Fragment for users to select a lineup */
+public class LineupSelectionFragment extends GuidedStepFragment {
+    public static final boolean DEBUG = false;
+    public static final String TAG = "LineupSelectionFragment";
+
+    public static final String KEY_TITLE = "title";
+    public static final String KEY_DESCRIPTION = "description";
+    private static final long ACTION_ID_STATUS = 1;
+    private static final long ACTION_ID_LINEUPS = 2;
+    private static final Pattern CHANNEL_NUMBER_DELIMITER = Pattern.compile("([ .-])");
+    private Activity mActivity;
+    private GuidedAction mStatusAction;
+    private String mPostcode;
+    private List<String> mChannelNumbers;
+    private Map<GuidedAction, Lineup> mActionLineupMap = new HashMap<>();
+
+    private AsyncTask<Void, Void, List<Pair<Lineup, Integer>>> mFetchLineupTask;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mActivity = getActivity();
+        mStatusAction =
+                new GuidedAction.Builder(mActivity)
+                        .id(ACTION_ID_STATUS)
+                        .title("Loading lineups")
+                        .description("please wait")
+                        .focusable(false)
+                        .build();
+
+        mFetchLineupTask =
+                new AsyncTask<Void, Void, List<Pair<Lineup, Integer>>>() {
+                    @Override
+                    protected List<Pair<Lineup, Integer>> doInBackground(Void... voids) {
+                        return lineupChannelMatchCount(getLineups(), getChannelNumbers());
+                    }
+
+                    @Override
+                    protected void onPostExecute(List<Pair<Lineup, Integer>> result) {
+                        List<GuidedAction> actions = new ArrayList<>();
+                        if (result.isEmpty()) {
+                            mStatusAction.setTitle("No lineup found for postcode: " + mPostcode);
+                            mStatusAction.setDescription("");
+                            mStatusAction.setFocusable(true);
+                            notifyActionChanged(findActionPositionById(ACTION_ID_STATUS));
+                            return;
+                        }
+                        mActionLineupMap = new HashMap<>();
+                        for (Pair<Lineup, Integer> pair : result) {
+                            Lineup lineup = pair.first;
+                            String title =
+                                    TextUtils.isEmpty(lineup.getName())
+                                            ? lineup.getId()
+                                            : lineup.getName();
+                            GuidedAction action =
+                                    new GuidedAction.Builder(getActivity())
+                                            .id(ACTION_ID_LINEUPS)
+                                            .title(title)
+                                            .description(pair.second + " channels match")
+                                            .build();
+                            mActionLineupMap.put(action, lineup);
+                            actions.add(action);
+                        }
+                        setActions(actions);
+                    }
+                };
+
+        super.onCreate(savedInstanceState);
+    }
+
+    @NonNull
+    @Override
+    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+        return new Guidance(
+                "LineupSelectionFragment", "LineupSelectionFragment Description", null, null);
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        View v = super.onCreateView(inflater, container, savedInstanceState);
+        if (!mFetchLineupTask.isCancelled()) {
+            mPostcode = getArguments().getString(ChannelScanFragment.KEY_POSTCODE);
+            mChannelNumbers =
+                    getArguments().getStringArrayList(ChannelScanFragment.KEY_CHANNEL_NUMBERS);
+            mFetchLineupTask.execute();
+        }
+        return v;
+    }
+
+    @Override
+    public void onDestroyView() {
+        mFetchLineupTask.cancel(true);
+        super.onDestroyView();
+    }
+
+    @Override
+    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+        actions.add(mStatusAction);
+    }
+
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+        if (action == null) {
+            return;
+        }
+        switch ((int) action.getId()) {
+            case (int) ACTION_ID_STATUS:
+                finishGuidedStepFragments();
+                break;
+            case (int) ACTION_ID_LINEUPS:
+                FragmentManager fm = getFragmentManager();
+                super.onGuidedActionClicked(action);
+                onLineupSelected(mActionLineupMap.get(action));
+                ResultFragment fragment = new ResultFragment();
+                Bundle args = new Bundle();
+                args.putString(KEY_TITLE, action.getTitle().toString());
+                args.putString(KEY_DESCRIPTION, action.getDescription().toString());
+                fragment.setArguments(args);
+                GuidedStepFragment.add(fm, fragment);
+                break;
+            default:
+        }
+    }
+
+    private List<Pair<Lineup, Integer>> lineupChannelMatchCount(
+            List<Lineup> lineups, List<String> localChannels) {
+        List<Pair<Lineup, Integer>> result = new ArrayList<>();
+        for (Lineup lineup : lineups) {
+            result.add(new Pair<>(lineup, getMatchCount(lineup.getChannels(), localChannels)));
+        }
+        // sort in decreasing order
+        Collections.sort(
+                result,
+                new Comparator<Pair<Lineup, Integer>>() {
+                    @Override
+                    public int compare(Pair<Lineup, Integer> pair, Pair<Lineup, Integer> other) {
+                        return Integer.compare(other.second, pair.second);
+                    }
+                });
+        return result;
+    }
+
+    private static int getMatchCount(List<String> lineupChannels, List<String> localChannels) {
+        int count = 0;
+        for (String lineupChannel : lineupChannels) {
+            if (TextUtils.isEmpty(lineupChannel)) {
+                continue;
+            }
+            List<String> parsedNumbers = parseChannelNumber(lineupChannel);
+            for (String channel : localChannels) {
+                if (TextUtils.isEmpty(channel)) {
+                    continue;
+                }
+                if (matchChannelNumber(parsedNumbers, parseChannelNumber(channel))) {
+                    if (DEBUG) {
+                        Log.d(TAG, lineupChannel + " matches " + channel);
+                    }
+                    count++;
+                    break;
+                }
+            }
+        }
+        return count;
+    }
+
+    private List<Lineup> getLineups() {
+        return new ArrayList<>(Lineups.query(mActivity.getContentResolver(), mPostcode));
+    }
+
+    private List<String> getChannelNumbers() {
+        return mChannelNumbers;
+    }
+
+    private void onLineupSelected(@Nullable Lineup lineup) {
+        if (lineup == null) {
+            return;
+        }
+        ContentValues values = new ContentValues();
+        values.put(EpgContract.EpgInputs.COLUMN_INPUT_ID, SampleTvInputService.INPUT_ID);
+        values.put(EpgContract.EpgInputs.COLUMN_LINEUP_ID, lineup.getId());
+
+        ContentResolver contentResolver = getActivity().getContentResolver();
+        EpgInput epgInput = EpgInputs.queryEpgInput(contentResolver, SampleTvInputService.INPUT_ID);
+        if (epgInput == null) {
+            contentResolver.insert(EpgContract.EpgInputs.CONTENT_URI, values);
+        } else {
+            values.put(EpgContract.EpgInputs.COLUMN_ID, epgInput.getId());
+            EpgInputs.update(contentResolver, EpgInput.createEpgChannel(values));
+        }
+    }
+
+    /**
+     * Parses the channel number string to a list of numbers (major number, minor number, etc.).
+     *
+     * @param channelNumber the display number of the channel
+     * @return a list of numbers
+     */
+    private static List<String> parseChannelNumber(String channelNumber) {
+        // TODO: add tests for this method
+        List<String> numbers =
+                new ArrayList<>(
+                        Arrays.asList(TextUtils.split(channelNumber, CHANNEL_NUMBER_DELIMITER)));
+        numbers.removeAll(Collections.singleton(""));
+        if (numbers.size() < 1 || numbers.size() > 2) {
+            Log.w(TAG, "unsupported channel number format: " + channelNumber);
+            return new ArrayList<>();
+        }
+        return numbers;
+    }
+
+    /**
+     * Checks whether two lists of channel numbers match or not. If the sizes are different,
+     * additional elements are ignore.
+     */
+    private static boolean matchChannelNumber(List<String> numbers, List<String> other) {
+        if (numbers.isEmpty() || other.isEmpty()) {
+            return false;
+        }
+        int i = 0;
+        int j = 0;
+        while (i < numbers.size() && j < other.size()) {
+            if (!numbers.get(i++).equals(other.get(j++))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/LocationFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/LocationFragment.java
new file mode 100644
index 0000000..9492e7e
--- /dev/null
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/LocationFragment.java
@@ -0,0 +1,91 @@
+/*
+ * 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.example.partnersupportsampletvinput;
+
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import java.util.List;
+
+/** Location Fragment for users to enter postal code */
+public class LocationFragment extends GuidedStepFragment {
+    public static final long ACTION_ID_ZIPCODE = 1;
+    private final GuidedAction mZipCodeAction =
+            new GuidedAction.Builder(getActivity())
+                    .id(ACTION_ID_ZIPCODE)
+                    .title("zip code")
+                    .description("please enter zip code")
+                    .editDescription("")
+                    .descriptionEditable(true)
+                    .build();
+
+    @NonNull
+    @Override
+    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+        return new Guidance("LocationFragment", "LocationFragment Description", null, null);
+    }
+
+    @Override
+    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+        actions.add(mZipCodeAction);
+    }
+
+    @Override
+    public void onCreateButtonActions(
+            @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+        actions.add(
+                new GuidedAction.Builder(getActivity())
+                        .id(GuidedAction.ACTION_ID_NEXT)
+                        .title("NEXT")
+                        .build());
+    }
+
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+        if (action == null) {
+            return;
+        }
+        FragmentManager fm = getFragmentManager();
+        switch ((int) action.getId()) {
+            case (int) ACTION_ID_ZIPCODE:
+                super.onGuidedActionClicked(action);
+                break;
+            case (int) GuidedAction.ACTION_ID_NEXT:
+                ChannelScanFragment fragment = new ChannelScanFragment();
+                Bundle args = new Bundle();
+                // TODO: validate the input postcode
+                args.putString(
+                        ChannelScanFragment.KEY_POSTCODE,
+                        mZipCodeAction.getDescription().toString());
+                fragment.setArguments(args);
+                GuidedStepFragment.add(fm, fragment);
+                break;
+            default:
+        }
+    }
+
+    @Override
+    public long onGuidedActionEditedAndProceed(GuidedAction action) {
+        long result = super.onGuidedActionEditedAndProceed(action);
+        action.setDescription(action.getEditDescription());
+        return result;
+    }
+}
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/ResultFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/ResultFragment.java
new file mode 100644
index 0000000..a1c17ac
--- /dev/null
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/ResultFragment.java
@@ -0,0 +1,95 @@
+/*
+ * 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.example.partnersupportsampletvinput;
+
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.content.Intent;
+import android.media.tv.TvInputInfo;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import com.google.android.tv.partner.support.EpgContract;
+import java.util.List;
+
+/** Result Fragment shows the setup result */
+public class ResultFragment extends GuidedStepFragment {
+    private static final long ACTION_ID_LINEUP = 1;
+
+    @NonNull
+    @Override
+    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+        return new Guidance("ResultFragment", "ResultFragment Description", null, null);
+    }
+
+    @Override
+    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+        String title = getArguments().getString(LineupSelectionFragment.KEY_TITLE);
+        String description = getArguments().getString(LineupSelectionFragment.KEY_DESCRIPTION);
+        actions.add(
+                new GuidedAction.Builder(getActivity())
+                        .id(ACTION_ID_LINEUP)
+                        .title("Selected Lineup")
+                        .description(title + " (" + description + ")")
+                        .focusable(false)
+                        .build());
+    }
+
+    @Override
+    public void onCreateButtonActions(
+            @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+        actions.add(
+                new GuidedAction.Builder(getActivity())
+                        .id(GuidedAction.ACTION_ID_FINISH)
+                        .title("FINISH")
+                        .build());
+        actions.add(
+                new GuidedAction.Builder(getActivity())
+                        .id(GuidedAction.ACTION_ID_CANCEL)
+                        .title("BACK")
+                        .build());
+    }
+
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+        if (action == null) {
+            return;
+        }
+        FragmentManager fm = getFragmentManager();
+        switch ((int) action.getId()) {
+            case (int) GuidedAction.ACTION_ID_CANCEL:
+                super.onGuidedActionClicked(action);
+                fm.popBackStack();
+                break;
+            case (int) GuidedAction.ACTION_ID_FINISH:
+                setResultAndFinishActivity(SampleTvInputService.INPUT_ID);
+                finishGuidedStepFragments();
+                break;
+            default:
+        }
+    }
+
+    private void setResultAndFinishActivity(String inputId) {
+        Intent data = new Intent();
+        data.putExtra(TvInputInfo.EXTRA_INPUT_ID, inputId);
+        data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true);
+        getActivity().setResult(Activity.RESULT_OK, data);
+        getActivity().finishActivity(SampleTvInputSetupActivity.REQUEST_CODE_START_SETUP_ACTIVITY);
+    }
+}
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputService.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputService.java
new file mode 100644
index 0000000..3edf228
--- /dev/null
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.example.partnersupportsampletvinput;
+
+import android.content.Context;
+import android.media.tv.TvInputService;
+import android.net.Uri;
+import android.view.Surface;
+
+/** SampleTvInputService */
+public class SampleTvInputService extends TvInputService {
+    public static final String INPUT_ID =
+            "com.example.partnersupportsampletvinput/.SampleTvInputService";
+
+    public BaseTvInputSessionImpl onCreateSession(String s) {
+        return null;
+    }
+
+    class BaseTvInputSessionImpl extends Session {
+
+        public BaseTvInputSessionImpl(Context context) {
+            super(context);
+        }
+
+        public void onRelease() {}
+
+        public boolean onSetSurface(Surface surface) {
+            return false;
+        }
+
+        public void onSetStreamVolume(float v) {}
+
+        public boolean onTune(Uri uri) {
+            return false;
+        }
+
+        public void onSetCaptionEnabled(boolean b) {}
+    }
+}
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputSetupActivity.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputSetupActivity.java
new file mode 100644
index 0000000..a0a7588
--- /dev/null
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/SampleTvInputSetupActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.example.partnersupportsampletvinput;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v17.leanback.app.GuidedStepFragment;
+
+/** The setup activity for partner support sample TV input. */
+public class SampleTvInputSetupActivity extends Activity {
+    public static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            WelcomeFragment wf = new WelcomeFragment();
+            wf.setArguments(getIntent().getExtras());
+            GuidedStepFragment.addAsRoot(this, wf, android.R.id.content);
+        }
+    }
+}
diff --git a/partner_support/samples/src/com/example/partnersupportsampletvinput/WelcomeFragment.java b/partner_support/samples/src/com/example/partnersupportsampletvinput/WelcomeFragment.java
new file mode 100644
index 0000000..286f34f
--- /dev/null
+++ b/partner_support/samples/src/com/example/partnersupportsampletvinput/WelcomeFragment.java
@@ -0,0 +1,67 @@
+/*
+ * 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.example.partnersupportsampletvinput;
+
+import android.app.FragmentManager;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
+import android.support.v17.leanback.widget.GuidedAction;
+import java.util.List;
+
+/** Welcome Fragment shows welcome information for users */
+public class WelcomeFragment extends GuidedStepFragment {
+
+    @NonNull
+    @Override
+    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+        return new Guidance("WelcomeFragment Title", "WelcomeFragment Description", null, null);
+    }
+
+    @Override
+    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
+        actions.add(
+                new GuidedAction.Builder(getActivity())
+                        .id(GuidedAction.ACTION_ID_NEXT)
+                        .title("NEXT")
+                        .build());
+        actions.add(
+                new GuidedAction.Builder(getActivity())
+                        .id(GuidedAction.ACTION_ID_CANCEL)
+                        .title("CANCEL")
+                        .build());
+    }
+
+    @Override
+    public void onGuidedActionClicked(GuidedAction action) {
+        if (action == null) {
+            return;
+        }
+        FragmentManager fm = getFragmentManager();
+        switch ((int) action.getId()) {
+            case (int) GuidedAction.ACTION_ID_NEXT:
+                GuidedStepFragment.add(fm, new LocationFragment());
+                break;
+            case (int) GuidedAction.ACTION_ID_CANCEL:
+                finishGuidedStepFragments();
+                break;
+            default:
+        }
+    }
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/AutoValue_EpgInput.java b/partner_support/src/com/google/android/tv/partner/support/AutoValue_EpgInput.java
new file mode 100644
index 0000000..aad51c7
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/AutoValue_EpgInput.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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.google.android.tv.partner.support;
+
+
+
+/**
+ * Hand copy of generated Autovalue class.
+ *
+ * TODO get autovalue working
+ */
+final class AutoValue_EpgInput extends EpgInput {
+
+    private final long id;
+    private final String inputId;
+    private final String lineupId;
+
+    AutoValue_EpgInput(
+            long id,
+            String inputId,
+            String lineupId) {
+        this.id = id;
+        if (inputId == null) {
+            throw new NullPointerException("Null inputId");
+        }
+        this.inputId = inputId;
+        if (lineupId == null) {
+            throw new NullPointerException("Null lineupId");
+        }
+        this.lineupId = lineupId;
+    }
+
+    @Override
+    public long getId() {
+        return id;
+    }
+
+    @Override
+    public String getInputId() {
+        return inputId;
+    }
+
+    @Override
+    public String getLineupId() {
+        return lineupId;
+    }
+
+    @Override
+    public String toString() {
+        return "EpgInput{"
+                + "id=" + id + ", "
+                + "inputId=" + inputId + ", "
+                + "lineupId=" + lineupId
+                + "}";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof EpgInput) {
+            EpgInput that = (EpgInput) o;
+            return (this.id == that.getId())
+                    && (this.inputId.equals(that.getInputId()))
+                    && (this.lineupId.equals(that.getLineupId()));
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int h$ = 1;
+        h$ *= 1000003;
+        h$ ^= (int) ((id >>> 32) ^ id);
+        h$ *= 1000003;
+        h$ ^= inputId.hashCode();
+        h$ *= 1000003;
+        h$ ^= lineupId.hashCode();
+        return h$;
+    }
+
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/AutoValue_Lineup.java b/partner_support/src/com/google/android/tv/partner/support/AutoValue_Lineup.java
new file mode 100644
index 0000000..076f8a2
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/AutoValue_Lineup.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 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.google.android.tv.partner.support;
+
+import android.support.annotation.Nullable;
+import java.util.List;
+
+/**
+ * Hand copy of generated Autovalue class.
+ *
+ * TODO get autovalue working
+ */
+
+final class AutoValue_Lineup extends Lineup {
+
+    private final String id;
+    private final int type;
+    private final String name;
+    private final List<String> channels;
+
+    AutoValue_Lineup(
+            String id,
+            int type,
+            @Nullable String name,
+            List<String> channels) {
+        if (id == null) {
+            throw new NullPointerException("Null id");
+        }
+        this.id = id;
+        this.type = type;
+        this.name = name;
+        if (channels == null) {
+            throw new NullPointerException("Null channels");
+        }
+        this.channels = channels;
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public int getType() {
+        return type;
+    }
+
+    @Nullable
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public List<String> getChannels() {
+        return channels;
+    }
+
+    @Override
+    public String toString() {
+        return "Lineup{"
+                + "id=" + id + ", "
+                + "type=" + type + ", "
+                + "name=" + name + ", "
+                + "channels=" + channels
+                + "}";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof Lineup) {
+            Lineup that = (Lineup) o;
+            return (this.id.equals(that.getId()))
+                    && (this.type == that.getType())
+                    && ((this.name == null) ? (that.getName() == null) : this.name.equals(that.getName()))
+                    && (this.channels.equals(that.getChannels()));
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int h$ = 1;
+        h$ *= 1000003;
+        h$ ^= id.hashCode();
+        h$ *= 1000003;
+        h$ ^= type;
+        h$ *= 1000003;
+        h$ ^= (name == null) ? 0 : name.hashCode();
+        h$ *= 1000003;
+        h$ ^= channels.hashCode();
+        return h$;
+    }
+
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/BaseCustomization.java b/partner_support/src/com/google/android/tv/partner/support/BaseCustomization.java
new file mode 100644
index 0000000..e40d90d
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/BaseCustomization.java
@@ -0,0 +1,93 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.Log;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Abstract class for TV Customization support.
+ *
+ * <p>Implement customization resources as needed in a child class.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
+public class BaseCustomization {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "BaseCustomization";
+
+    // TODO move common aosp library
+
+    // TODO cache resource if performance suffers.
+
+    private static final String RES_TYPE_BOOLEAN = "bool";
+
+    private final String packageName;
+
+    protected BaseCustomization(Context context, String[] permissions) {
+        packageName = getFirstPackageNameWithPermissions(context, permissions);
+    }
+
+    public final String getPackageName() {
+        return packageName;
+    }
+
+    private static String getFirstPackageNameWithPermissions(
+            Context context, String[] permissions) {
+
+        List<PackageInfo> packageInfos =
+                context.getPackageManager().getPackagesHoldingPermissions(permissions, 0);
+        if (DEBUG) {
+            Log.d(TAG, "These packages have " + Arrays.toString(permissions) + ": " + packageInfos);
+        }
+        return packageInfos == null || packageInfos.size() == 0
+                ? ""
+                : packageInfos.get(0).packageName;
+    }
+
+    private static Resources getResourceForPackage(Context context, String packageName)
+            throws PackageManager.NameNotFoundException {
+        return context.getPackageManager().getResourcesForApplication(packageName);
+    }
+
+    public final Optional<Boolean> getBooleanResource(Context context, String resourceName) {
+        if (resourceName.isEmpty()) {
+            return Optional.empty();
+        }
+        try {
+            Resources res = getResourceForPackage(context, packageName);
+            int resId =
+                    res == null
+                            ? 0
+                            : res.getIdentifier(resourceName, RES_TYPE_BOOLEAN, packageName);
+            if (DEBUG) {
+                Log.d(TAG, "Boolean resource  " + resourceName + " has  " + resId);
+            }
+            return resId == 0 ? Optional.empty() : Optional.of(res.getBoolean(resId));
+        } catch (PackageManager.NameNotFoundException e) {
+            return Optional.empty();
+        }
+    }
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/EpgContract.java b/partner_support/src/com/google/android/tv/partner/support/EpgContract.java
new file mode 100644
index 0000000..1f47b15
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/EpgContract.java
@@ -0,0 +1,201 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import android.content.ContentUris;
+import android.net.Uri;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The contract between the EPG provider and applications. Contains definitions for the supported
+ * URIs and columns.
+ *
+ * <h3>Overview</h3>
+ *
+ * <p>
+ *
+ * <p>EpgContract defines a basic database of EPG data for third party inputs. The information is
+ * stored in {@link Lineups} and {@link EpgInputs} tables.
+ *
+ * <p>
+ *
+ * <ul>
+ *   <li>A row in the {@link Lineups} table represents information TV Lineups for postal codes.
+ *   <li>A row in the {@link EpgInputs} table represents partner or 3rd party inputs with the lineup
+ *       that input uses.
+ * </ul>
+ */
+public final class EpgContract {
+
+    /** The authority for the EPG provider. */
+    public static final String AUTHORITY = "com.google.android.tv.data.epg";
+
+    /** The delimiter between channel numbers. */
+    public static final String CHANNEL_NUMBER_DELIMITER = ", ";
+
+    /**
+     * A constant of the key for intent to indicate whether cloud EPG is used.
+     *
+     * <p>Value type: Boolean
+     */
+    public static final String EXTRA_USE_CLOUD_EPG = "com.google.android.tv.extra.USE_CLOUD_EPG";
+
+    /**
+     * Returns the list of channels as a CSV string.
+     *
+     * <p>Any commas in the original channels are converted to periods.
+     */
+    public static String toChannelString(List<String> channels) {
+        // TODO use a StringJoiner if we set the min SDK to N
+        StringBuilder result = new StringBuilder();
+        for (String channel : channels) {
+            channel = channel.replace(",", ".");
+            if (result.length() > 0) {
+                result.append(CHANNEL_NUMBER_DELIMITER);
+            }
+            result.append(channel);
+        }
+        return result.toString();
+    }
+
+    /** Returns a list of channels. */
+    public static List<String> toChannelList(String channelString) {
+        return channelString == null
+                ? Collections.EMPTY_LIST
+                : Arrays.asList(channelString.split(CHANNEL_NUMBER_DELIMITER));
+    }
+
+    /** Column definitions for the EPG lineups table. */
+    public static final class Lineups {
+
+        /** Base content:// style URI for all lines in a postal code. */
+        private static final Uri LINEUPS_IN_POSTAL_CODE_URI =
+                Uri.parse("content://" + AUTHORITY + "/lineups/postal_code");
+
+        /** The MIME type of a directory of TV channels. */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/lineup";
+
+        /** The MIME type of a single TV channel. */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/lineup";
+
+        /** The content:// style URI all lineups in a postal code. */
+        public static Uri uriForPostalCode(String postalCode) {
+            return LINEUPS_IN_POSTAL_CODE_URI.buildUpon().appendPath(postalCode).build();
+        }
+
+        /**
+         * The line up id.
+         *
+         * <p>This is a opaque string that should not be parsed.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_ID = "_ID";
+
+        /**
+         * The line up name that is displayed to the user.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_NAME = "NAME";
+
+        /**
+         * The channel numbers that are supported by this lineup that is displayed to the user.
+         *
+         * <p>Comma separated value of channel numbers
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_CHANNELS = "CHANNELS";
+
+        /**
+         * The line up type.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_TYPE = "TYPE";
+
+        private Lineups() {}
+    }
+
+    /** Column definitions for the EPG inputs table. */
+    public static final class EpgInputs {
+
+        /** The content:// style URI for this table. */
+        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/epg_input");
+
+        /** The MIME type of a directory of TV channels. */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/epg_input";
+
+        /** The MIME type of a single TV channel. */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/epg_input";
+
+        /**
+         * Builds a URI that points to a specific input.
+         *
+         * @param rowId The ID of the input to point to.
+         */
+        public static final Uri buildUri(long rowId) {
+            return ContentUris.withAppendedId(CONTENT_URI, rowId);
+        }
+
+        /**
+         * The unique ID for a row.
+         *
+         * <p>Type: INTEGER (long)
+         */
+        public static final String COLUMN_ID = "_ID";
+
+        /**
+         * The name of the package that owns the current row.
+         *
+         * <p>The EPG provider fills in this column with the name of the package that provides the
+         * initial data of the row. If the package is later uninstalled, the rows it owns are
+         * automatically removed from the tables.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_PACKAGE_NAME = "PACKAGE_NAME";
+
+        /**
+         * The ID of the TV input service that provides this TV channel.
+         *
+         * <p>Use {@link android.media.tv.TvContract#buildInputId} to build the ID.
+         *
+         * <p>This is a required field.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_INPUT_ID = "INPUT_ID";
+        /**
+         * The line up id.
+         *
+         * <p>This is a opaque string that should not be parsed.
+         *
+         * <p>This is a required field.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_LINEUP_ID = "LINEUP_ID";
+
+        private EpgInputs() {}
+    }
+
+    private EpgContract() {}
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/EpgInput.java b/partner_support/src/com/google/android/tv/partner/support/EpgInput.java
new file mode 100644
index 0000000..82cc463
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/EpgInput.java
@@ -0,0 +1,64 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import android.content.ContentValues;
+
+/**
+ * Value class representing a TV Input that uses Live TV EPG.
+ *
+ * @see {@link EpgContract.EpgInputs}
+ */
+// TODO(b/72052568): Get autovalue to work in aosp master
+public abstract class EpgInput {
+
+    public static EpgInput createEpgChannel(long id, String inputId, String lineupId) {
+        return new AutoValue_EpgInput(id, inputId, lineupId);
+    }
+
+    public static EpgInput createEpgChannel(ContentValues contentValues) {
+        long id = contentValues.getAsLong(EpgContract.EpgInputs.COLUMN_ID);
+        String inputId = contentValues.getAsString(EpgContract.EpgInputs.COLUMN_INPUT_ID);
+        String lineupId = contentValues.getAsString(EpgContract.EpgInputs.COLUMN_LINEUP_ID);
+        return createEpgChannel(id, inputId, lineupId);
+    }
+
+    /** The unique ID for a row. */
+    public abstract long getId();
+
+    /**
+     * The ID of the TV input service that provides this TV channel.
+     *
+     * <p>Use {@link android.media.tv.TvContract#buildInputId} to build the ID.
+     */
+    public abstract String getInputId();
+
+    /**
+     * The line up id.
+     *
+     * <p>This is a opaque string that should not be parsed.
+     */
+    public abstract String getLineupId();
+
+    public final ContentValues toContentValues() {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(EpgContract.EpgInputs.COLUMN_ID, getId());
+        contentValues.put(EpgContract.EpgInputs.COLUMN_INPUT_ID, getInputId());
+        contentValues.put(EpgContract.EpgInputs.COLUMN_LINEUP_ID, getLineupId());
+        return contentValues;
+    }
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/EpgInputs.java b/partner_support/src/com/google/android/tv/partner/support/EpgInputs.java
new file mode 100644
index 0000000..53485ec
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/EpgInputs.java
@@ -0,0 +1,84 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Static utilities for {@link EpgInput}. */
+public final class EpgInputs {
+    // TODO create a fake EpgContentProvider for testing.
+
+    /** Returns {@link EpgInput} for {@code inputId} or null if not found. */
+    @WorkerThread
+    @Nullable
+    public static EpgInput queryEpgInput(ContentResolver contentResolver, String inputId) {
+
+        for (EpgInput epgInput : queryEpgInputs(contentResolver)) {
+            if (inputId.equals(epgInput.getInputId())) {
+                return epgInput;
+            }
+        }
+        return null;
+    }
+
+    /** Returns all {@link EpgInput}. */
+    @WorkerThread
+    public static Set<EpgInput> queryEpgInputs(ContentResolver contentResolver) {
+        try (Cursor cursor =
+                contentResolver.query(EpgContract.EpgInputs.CONTENT_URI, null, null, null, null)) {
+            if (cursor == null) {
+                return Collections.emptySet();
+            }
+            HashSet<EpgInput> result = new HashSet<>(cursor.getCount());
+            while (cursor.moveToNext()) {
+                ContentValues contentValues = new ContentValues();
+                DatabaseUtils.cursorRowToContentValues(cursor, contentValues);
+                result.add(EpgInput.createEpgChannel(contentValues));
+            }
+            return result;
+        }
+    }
+
+    /** Insert an {@link EpgInput}. */
+    @WorkerThread
+    @Nullable
+    public static Uri insert(ContentResolver contentResolver, EpgInput epgInput) {
+        return contentResolver.insert(
+                EpgContract.EpgInputs.CONTENT_URI, epgInput.toContentValues());
+    }
+
+    /** Update an {@link EpgInput}. */
+    @WorkerThread
+    public static int update(ContentResolver contentResolver, EpgInput epgInput) {
+        return contentResolver.update(
+                EpgContract.EpgInputs.buildUri(epgInput.getId()),
+                epgInput.toContentValues(),
+                null,
+                null);
+    }
+
+    private EpgInputs() {}
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/Lineup.java b/partner_support/src/com/google/android/tv/partner/support/Lineup.java
new file mode 100644
index 0000000..6123eeb
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/Lineup.java
@@ -0,0 +1,81 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import android.content.ContentValues;
+import android.support.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Value class for {@link com.google.android.tv.partner.support.EpgContract.Lineups} */
+// TODO(b/72052568): Get autovalue to work in aosp master
+public abstract class Lineup {
+    /** Lineup type for cable. */
+    public static final int LINEUP_CABLE = 0;
+
+    /** Lineup type for satelite. */
+    public static final int LINEUP_SATELLITE = 1;
+
+    /** Lineup type for broadcast digital. */
+    public static final int LINEUP_BROADCAST_DIGITAL = 2;
+
+    /** Lineup type for broadcast analog. */
+    public static final int LINEUP_BROADCAST_ANALOG = 3;
+
+    /** Lineup type for IPTV. */
+    public static final int LINEUP_IPTV = 4;
+
+    /**
+     * Indicates the lineup is either satellite, cable or IPTV but we are not sure which specific
+     * type.
+     */
+    public static final int LINEUP_MVPD = 5;
+
+    /** Lineup type for Internet. */
+    public static final int LINEUP_INTERNET = 6;
+
+    /** Lineup type for other. */
+    public static final int LINEUP_OTHER = 7;
+
+    public static Lineup createLineup(String id, int type, String name, List<String> channels) {
+        return new AutoValue_Lineup(
+                id, type, name, Collections.unmodifiableList(new ArrayList<>(channels)));
+    }
+
+    public static Lineup createLineup(ContentValues contentValues) {
+        String id = contentValues.getAsString(EpgContract.Lineups.COLUMN_ID);
+        int type = contentValues.getAsInteger(EpgContract.Lineups.COLUMN_TYPE);
+        String name = contentValues.getAsString(EpgContract.Lineups.COLUMN_NAME);
+        String channels = contentValues.getAsString(EpgContract.Lineups.COLUMN_CHANNELS);
+        List<String> channelList = EpgContract.toChannelList(channels);
+        return new AutoValue_Lineup(id, type, name, Collections.unmodifiableList(channelList));
+    }
+
+    /** The ID of this lineup. */
+    public abstract String getId();
+
+    /** The type associated with this lineup. */
+    public abstract int getType();
+
+    /** The human readable name associated with this lineup. */
+    @Nullable
+    public abstract String getName();
+
+    /** An unmodifiable list of channel numbers that this lineup has. */
+    public abstract List<String> getChannels();
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/Lineups.java b/partner_support/src/com/google/android/tv/partner/support/Lineups.java
new file mode 100644
index 0000000..84f02bf
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/Lineups.java
@@ -0,0 +1,45 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Static utilities for {@link Lineup}. */
+public final class Lineups {
+
+    public static Set<Lineup> query(ContentResolver contentResolver, String postalCode) {
+        Set<Lineup> result = new HashSet<>();
+        try (Cursor cursor =
+                contentResolver.query(
+                        EpgContract.Lineups.uriForPostalCode(postalCode), null, null, null, null)) {
+            ContentValues contentValues = new ContentValues();
+            while (cursor.moveToNext()) {
+                contentValues.clear();
+                DatabaseUtils.cursorRowToContentValues(cursor, contentValues);
+                result.add(Lineup.createLineup(contentValues));
+            }
+            return result;
+        }
+    }
+
+    private Lineups() {}
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/PartnerCustomizations.java b/partner_support/src/com/google/android/tv/partner/support/PartnerCustomizations.java
new file mode 100644
index 0000000..7ff168e
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/PartnerCustomizations.java
@@ -0,0 +1,43 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+
+/** Customizations available to partners */
+@TargetApi(Build.VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated
+public final class PartnerCustomizations extends BaseCustomization {
+
+    private static final String[] CUSTOMIZE_PERMISSIONS = {
+        "com.google.android.tv.permission.CUSTOMIZE_TV_APP"
+    };
+
+    public static final String TVPROVIDER_ALLOWS_SYSTEM_INSERTS_TO_PROGRAM_TABLE =
+            "tvprovider_allows_system_inserts_to_program_table";
+
+    public PartnerCustomizations(Context context) {
+        super(context, CUSTOMIZE_PERMISSIONS);
+    }
+
+    public boolean doesTvProviderAllowSystemInsertsToProgramTable(Context context) {
+        return getBooleanResource(context, TVPROVIDER_ALLOWS_SYSTEM_INSERTS_TO_PROGRAM_TABLE)
+                .orElse(false);
+    }
+}
diff --git a/partner_support/src/com/google/android/tv/partner/support/TunerSetupUtils.java b/partner_support/src/com/google/android/tv/partner/support/TunerSetupUtils.java
new file mode 100644
index 0000000..e25d583
--- /dev/null
+++ b/partner_support/src/com/google/android/tv/partner/support/TunerSetupUtils.java
@@ -0,0 +1,124 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/** Utility class for providing tuner setup. */
+public class TunerSetupUtils {
+    private static final String TAG = "TunerSetupUtils";
+
+    private static final Pattern CHANNEL_NUMBER_DELIMITER = Pattern.compile("([ .-])");
+
+    public static List<Pair<Lineup, Integer>> lineupChannelMatchCount(
+            List<Lineup> lineups, List<String> localChannels) {
+        List<Pair<Lineup, Integer>> result = new ArrayList<>();
+        List<List<String>> parsedLocalChannels = parseChannelNumbers(localChannels);
+        for (Lineup lineup : lineups) {
+            result.add(
+                    new Pair<>(lineup, getMatchCount(lineup.getChannels(), parsedLocalChannels)));
+        }
+        // sort in decreasing order
+        Collections.sort(
+                result,
+                new Comparator<Pair<Lineup, Integer>>() {
+                    @Override
+                    public int compare(Pair<Lineup, Integer> pair, Pair<Lineup, Integer> other) {
+                        return Integer.compare(other.second, pair.second);
+                    }
+                });
+        return result;
+    }
+
+    @VisibleForTesting
+    static int getMatchCount(List<String> lineupChannels, List<List<String>> parsedLocalChannels) {
+        int count = 0;
+        List<List<String>> parsedLineupChannels = parseChannelNumbers(lineupChannels);
+        for (List<String> parsedLineupChannel : parsedLineupChannels) {
+            for (List<String> parsedLocalChannel : parsedLocalChannels) {
+                if (matchChannelNumber(parsedLineupChannel, parsedLocalChannel)) {
+                    count++;
+                    break;
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Parses the channel number string to a list of numbers (major number, minor number, etc.).
+     *
+     * @param channelNumber the display number of the channel
+     * @return a list of numbers
+     */
+    @VisibleForTesting
+    static List<String> parseChannelNumber(String channelNumber) {
+        List<String> numbers =
+                new ArrayList<>(
+                        Arrays.asList(TextUtils.split(channelNumber, CHANNEL_NUMBER_DELIMITER)));
+        numbers.removeAll(Collections.singleton(""));
+        if (numbers.size() < 1 || numbers.size() > 2) {
+            Log.w(TAG, "unsupported channel number format: " + channelNumber);
+            return new ArrayList<>();
+        }
+        return numbers;
+    }
+
+    /**
+     * Parses a list of channel numbers. See {@link #parseChannelNumber(String)}.
+     *
+     * @param channelNumbers a list of channel display numbers
+     */
+    @VisibleForTesting
+    static List<List<String>> parseChannelNumbers(List<String> channelNumbers) {
+        List<List<String>> numbers = new ArrayList<>(channelNumbers.size());
+        for (String channelNumber : channelNumbers) {
+            if (!TextUtils.isEmpty(channelNumber)) {
+                numbers.add(parseChannelNumber(channelNumber));
+            }
+        }
+        return numbers;
+    }
+
+    /**
+     * Checks whether two lists of channel numbers match or not. If the sizes are different,
+     * additional elements are ignore.
+     */
+    @VisibleForTesting
+    static boolean matchChannelNumber(List<String> numbers, List<String> other) {
+        if (numbers.isEmpty() || other.isEmpty()) {
+            return false;
+        }
+        int i = 0;
+        int j = 0;
+        while (i < numbers.size() && j < other.size()) {
+            if (!numbers.get(i++).equals(other.get(j++))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/BaseCustomizationTest.java b/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/BaseCustomizationTest.java
new file mode 100644
index 0000000..a6292e3
--- /dev/null
+++ b/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/BaseCustomizationTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import com.android.tv.testing.TestSingletonApp;
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link BaseCustomization}. */
+
+// TODO: move to partner-support
+
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(
+    manifest =
+            "//third_party/java_src/android_app/live_channels/common/src"
+                    + "/com/android/tv/common"
+                    + ":common/AndroidManifest.xml",
+    sdk = ConfigConstants.SDK,
+    application = TestSingletonApp.class
+)
+public class BaseCustomizationTest {
+
+    private static final String[] PERMISSIONS = {"com.example.permission"};
+
+    private Context context;
+    private PackageManager packageManager;
+
+    @Before
+    public void setUp() {
+        context = mock(Context.class);
+        // TODO(b/68809029): Don't mock PackageManager
+        packageManager = mock(PackageManager.class);
+        when(context.getPackageManager()).thenReturn(packageManager);
+        List<PackageInfo> packageInfos = new ArrayList<>();
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = "com.example.custom";
+        packageInfos.add(packageInfo);
+        when(packageManager.getPackagesHoldingPermissions(PERMISSIONS, 0)).thenReturn(packageInfos);
+    }
+
+    @Test
+    public void getPackageName_example() {
+        TestCustomizations testCustomizations = new TestCustomizations(context);
+        assertThat(testCustomizations.getPackageName()).isEqualTo("com.example.custom");
+    }
+
+    @Test
+    public void getPackageName_none() {
+        TestCustomizations testCustomizations =
+                new TestCustomizations(RuntimeEnvironment.application);
+        assertThat(testCustomizations.getPackageName()).isEmpty();
+    }
+
+    static class TestCustomizations extends BaseCustomization {
+        protected TestCustomizations(Context context) {
+            super(context, PERMISSIONS);
+        }
+    }
+}
diff --git a/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/TunerSetupUtilsTest.java b/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/TunerSetupUtilsTest.java
new file mode 100644
index 0000000..d9b48cf
--- /dev/null
+++ b/partner_support/tests/robotests/javatests/com/google/android/tv/partner/support/TunerSetupUtilsTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.google.android.tv.partner.support;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tv.testing.constants.ConfigConstants;
+import com.google.thirdparty.robolectric.GoogleRobolectricTestRunner;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link TunerSetupUtils} */
+@RunWith(GoogleRobolectricTestRunner.class)
+@Config(sdk = ConfigConstants.SDK)
+public class TunerSetupUtilsTest {
+
+    @Test
+    public void getMatchCount_allMatch() {
+        List<String> channelNumbers1 = Arrays.asList("1.1", "1.2", "2.3");
+        List<List<String>> parsedChannelNumbers2 =
+                TunerSetupUtils.parseChannelNumbers(Arrays.asList("1.1", "1.2", "2.3"));
+        assertThat(TunerSetupUtils.getMatchCount(channelNumbers1, parsedChannelNumbers2))
+                .isEqualTo(3);
+    }
+
+    @Test
+    public void getMatchCount_someMatch() {
+        List<String> channelNumbers1 = Arrays.asList("1.1", "1.2", "2.3");
+        List<List<String>> parsedChannelNumbers2 =
+                TunerSetupUtils.parseChannelNumbers(Arrays.asList("1.0", "1.1", "2"));
+        assertThat(TunerSetupUtils.getMatchCount(channelNumbers1, parsedChannelNumbers2))
+                .isEqualTo(2);
+    }
+
+    @Test
+    public void getMatchCount_noMatch() {
+        List<String> channelNumbers1 = Arrays.asList("1.1", "1.2", "2.3");
+        List<List<String>> parsedChannelNumbers2 =
+                TunerSetupUtils.parseChannelNumbers(Arrays.asList("1.0", "1.3", "3"));
+        assertThat(TunerSetupUtils.getMatchCount(channelNumbers1, parsedChannelNumbers2))
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void parseChannelNumber_majorNumberOnly() {
+        assertThat(TunerSetupUtils.parseChannelNumber("11")).containsExactly("11");
+    }
+
+    @Test
+    public void parseChannelNumber_majorAndMinorNumbers() {
+        assertThat(TunerSetupUtils.parseChannelNumber("2-11")).containsExactly("2", "11");
+    }
+
+    @Test
+    public void parseChannelNumber_containsExtraSpaces() {
+        assertThat(TunerSetupUtils.parseChannelNumber(" 2 - 11 ")).containsExactly("2", "11");
+    }
+
+    @Test
+    public void parseChannelNumber_tooLong() {
+        assertThat(TunerSetupUtils.parseChannelNumber("2-11-1")).isEmpty();
+    }
+
+    @Test
+    public void parseChannelNumber_empty() {
+        assertThat(TunerSetupUtils.parseChannelNumber("  ")).isEmpty();
+    }
+
+    @Test
+    public void matchChannelNumber_match_majorAndMinorNumbers() {
+        assertThat(
+                        TunerSetupUtils.matchChannelNumber(
+                                Arrays.asList("22", "1"), Arrays.asList("22", "1")))
+                .isTrue();
+    }
+
+    @Test
+    public void matchChannelNumber_match_majorNumber() {
+        assertThat(
+                        TunerSetupUtils.matchChannelNumber(
+                                Collections.singletonList("22"), Collections.singletonList("22")))
+                .isTrue();
+    }
+
+    @Test
+    public void matchChannelNumber_match_differentSize() {
+        assertThat(
+                        TunerSetupUtils.matchChannelNumber(
+                                Arrays.asList("22", "1"), Collections.singletonList("22")))
+                .isTrue();
+    }
+
+    @Test
+    public void matchChannelNumber_notMatch() {
+        assertThat(
+                        TunerSetupUtils.matchChannelNumber(
+                                Arrays.asList("22", "1"), Collections.singletonList("11")))
+                .isFalse();
+    }
+}
diff --git a/proguard.flags b/proguard.flags
index 0edd14f..69b1786 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -27,12 +27,6 @@
 -dontwarn com.google.android.volley.**
 -dontwarn com.google.android.common.**
 
-# Keep the methods called from native code.
--keepclasseswithmembers class com.android.tv.tuner.TunerHal {
-    int openDvbFrontEndFd();
-    int openDvbDemuxFd();
-    int openDvbDvrFd();
-}
 -keepclasseswithmembers class com.android.tv.tuner.*DataSource {
     int readAt(long, byte[], int, int);
     long getSize();
@@ -42,10 +36,28 @@
    native <methods>;
 }
 
-# Keep method which is used for reflection.
--keep @com.android.tv.common.annotation.UsedByReflection class *  {*;}
--keepclasseswithmembers class * {
-    @com.android.tv.common.annotation.UsedByReflection <methods>;
+# Configuration of proguard via annotations. Apply them to
+# the elements of your program not only to ensure correct proguard
+# functionality, but to document non-obvious entry points to your code to make
+# it survive refactorings.
+
+# Annotations are implemented as attributes, so we have to explicitly keep them.
+# Catch all which encompasses attributes like RuntimeVisibleParameterAnnotations
+# and RuntimeVisibleTypeAnnotations
+-keepattributes RuntimeVisible*Annotation*
+
+# JNI is an entry point that's hard to keep track of, so there's
+# an annotation to mark fields and methods used by native code.
+
+# Keep the annotations that proguard needs to process.
+-keep class com.android.tv.common.annotation.UsedBy*
+
+# Just because native code accesses members of a class, does not mean that the
+# class itself needs to be annotated - only annotate classes that are
+# referenced themselves in native code.
+-keep @com.android.tv.common.annotation.UsedBy* class *
+-keepclassmembers class * {
+    @com.android.tv.common.annotation.UsedBy* *;
 }
 
 # For tests
@@ -56,4 +68,3 @@
 
 # Grpc used by epg via reflection
 -keep class io.grpc.internal.DnsNameResolverProvider
-
diff --git a/res/drawable-xhdpi/ic_live_channels.png b/res/drawable-xhdpi/ic_live_channels.png
new file mode 100644
index 0000000..bb1c2d9
--- /dev/null
+++ b/res/drawable-xhdpi/ic_live_channels.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_live_channels_96x96.png
similarity index 100%
rename from res/drawable-xhdpi/ic_launcher.png
rename to res/drawable-xhdpi/ic_live_channels_96x96.png
Binary files differ
diff --git a/res/layout/activity_dvr_history.xml b/res/layout/activity_dvr_history.xml
new file mode 100644
index 0000000..c44bc8a
--- /dev/null
+++ b/res/layout/activity_dvr_history.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/dvr_history"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/empty_info_screen"
+        android:layout_width="600dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:fontFamily="@string/font"
+        android:textSize="@dimen/tvview_block_text_size"
+        android:lineSpacingExtra="@dimen/tvview_block_line_spacing_extra"
+        android:textColor="@color/tvview_block_text_color" />
+
+    <FrameLayout android:id="@+id/fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/lb_details_overview.xml b/res/layout/details_overview.xml
similarity index 100%
rename from res/layout/lb_details_overview.xml
rename to res/layout/details_overview.xml
diff --git a/res/layout/dvr_recording_card_view.xml b/res/layout/dvr_recording_card_view.xml
index 53a7cf3..3e95351 100644
--- a/res/layout/dvr_recording_card_view.xml
+++ b/res/layout/dvr_recording_card_view.xml
@@ -15,7 +15,7 @@
   -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tv="http://schemas.android.com/apk/res/com.android.tv"
+    xmlns:tv="http://schemas.android.com/apk/res-auto"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content">
 
diff --git a/res/layout/tunable_tv_view.xml b/res/layout/tunable_tv_view.xml
index 00c9908..549d053 100644
--- a/res/layout/tunable_tv_view.xml
+++ b/res/layout/tunable_tv_view.xml
@@ -17,6 +17,27 @@
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android" >
 
+    <View android:id="@+id/channel_up"
+        android:layout_width="wrap_content"
+        android:focusable="false"
+        android:focusableInTouchMode="true"
+        android:layout_height="1dp"
+        android:layout_gravity="top" />
+    <View android:id="@+id/placeholder"
+        android:layout_width="1dp"
+        android:layout_height="1dp"
+        android:focusable="false"
+        android:focusableInTouchMode="true"
+        android:focusedByDefault="true"
+        android:layout_gravity="center" />
+
+    <View android:id="@+id/channel_down"
+        android:layout_width="wrap_content"
+        android:focusable="false"
+        android:focusableInTouchMode="true"
+        android:layout_height="1dp"
+        android:layout_gravity="bottom" />
+
     <com.android.tv.ui.AppLayerTvView android:id="@+id/tv_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index a6c8735..e57b054 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -65,7 +65,7 @@
         <item>Browse content from your apps with a familiar guide and friendly interface,
 \njust like channels on TV.</item>
         <item>Add more channels by installing apps that offer live channels.
-\nFind compatible apps in Google Play Store by using the link within the TV menu.</item>
+\nFind compatible apps in the online store by using the link within the TV menu.</item>
         <!-- Refer to @string/settings_channel_source_item_setup for "Channel sources" menu
              and @string/options_item_settings for "Settings" menu. -->
         <item>Set up your newly installed channel sources to customize your channel list.
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 57049ab..cea4ee6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -129,7 +129,7 @@
     <string name="multi_audio_unknown_language">Unknown language</string>
 
     <!-- Default string for unknown language used in "Closed Caption" options. [CHAR LIMIT=30] -->
-    <string name="closed_caption_unknown_language">Closed captions %1$d</string>
+    <string name="closed_caption_unknown_language">Closed captions <xliff:g id="unknown_language_index" example="1">%1$d</xliff:g></string>
 
     <!-- Inside "Closed captions" option side panel -->
     <eat-comment />
@@ -162,7 +162,7 @@
     <!-- Label of audio track with eight audio channels [CHAR LIMIT=30] -->
     <string name="multi_audio_channel_surround_8">7.1 surround</string>
     <!-- Label of audio track with other audio channels than 1, 2, 6, and 8 [CHAR LIMIT=30] -->
-    <string name="multi_audio_channel_suffix">%d channels</string>
+    <string name="multi_audio_channel_suffix"><xliff:g id="audio_channel_count" example="5">%d</xliff:g> channels</string>
 
     <!-- Inside "Edit Channels for a specific input" option side panel -->
     <eat-comment />
@@ -330,15 +330,6 @@
     <!-- Toast message when an user couldn't pass the PIN confirmation. [CHAR LIMIT=NONE] -->
     <string name="pin_toast_not_match">Try again, PIN doesn\'t match</string>
 
-    <!-- Title of postal/zip code input guided step fragment  [CHAR LIMIT=30] -->
-    <string name="postal_code_guidance_title">Enter your ZIP Code.</string>
-    <!-- Description of postal/zip code input guided step fragment  [CHAR LIMIT=NONE] -->
-    <string name="postal_code_guidance_description">Live TV app will use the ZIP Code to provide a complete program guide for the TV channels.</string>
-    <!-- Description of postal/zip code input edit text view to prompt users entering ZIP Code  [CHAR LIMIT=30] -->
-    <string name="postal_code_action_description">Enter your ZIP Code</string>
-    <!-- Warning message shown in description field of postal/zip code input edit text view when user enters an invalid ZIP Code and presses Done [CHAR LIMIT=30] -->
-    <string name="postal_code_invalid_warning">Invalid ZIP Code</string>
-
     <!-- menu for "Settings" option -->
     <eat-comment />
     <!--  Title of "Settings" option. [CHAR LIMIT=30] -->
@@ -522,6 +513,13 @@
     <!-- Content text of DVR recording service's notification during updating schedules. -->
     <string name="dvr_notification_content_text_loading" translatable="false">Live TV are updating recording schedules.</string>
 
+    <!-- Default content title of tuner installing notifications. -->
+    <string name="tuner_install_notification_content_title" translatable="false">Install <xliff:g id="tuner_package" example="Tuner package">%s</xliff:g></string>
+    <!-- Default application name for tuner installing notifications. -->
+    <string name="tuner_install_default_application_name" translatable="false">Tuner Package</string>
+    <!-- Default content text of tuner installing notifications. -->
+    <string name="tuner_install_notification_content_text" translatable="false">Install tuner package to watch tuner channels.</string>
+
     <!-- EPG Search strings. -->
     <!-- Remove translatable="false" once UI is finalized -->
     <string name="search_result_no_result" translatable="false">No result</string>
@@ -533,7 +531,7 @@
     <string name="setup_sources_text">Set up your sources</string>
     <!-- Description for channel sources screen in onboarding. -->
     <string name="setup_sources_description">Live channels combines the experience of traditional TV channels with streaming channels provided by apps.
-\n\nGet started by setting up the channel sources already installed. Or browse Google Play Store for more apps that offer live channels.</string>
+\n\nGet started by setting up the channel sources already installed. Or browse the online store for more apps that offer live channels.</string>
 
     <!-- Menu item label to start DVR manager UI. -->
     <string name="channels_item_dvr">Recordings &amp; schedules</string>
@@ -574,8 +572,12 @@
     <!-- Toast message that a new recording schedule of the current program has been created
          from the user action. -->
     <string name="dvr_msg_current_program_scheduled">Recording <xliff:g id="programName" example="Big bang theory">%1$s</xliff:g> from now to <xliff:g id="endTime" example="12:30 PM">%2$s</xliff:g></string>
+    <!-- Description of a card view to show Recording (DVR) history.[CHAR LIMIT=25]-->
+    <string name="dvr_history_card_view_title">Recording History</string>
     <!-- Description of a card view to show full list of scheduled recordings. [CHAR LIMIT=25] -->
     <string name="dvr_full_schedule_card_view_title">Full schedule</string>
+    <!-- Description of failed recordings. [CHAR LIMIT=25] -->
+    <string name="dvr_recording_failed">Recording Failed</string>
     <!-- Description of how many following days the schedule list will show. [CHAR LIMIT=25] -->
     <plurals name="dvr_full_schedule_card_view_content">
         <item quantity="one">Next %1$d day</item>
@@ -752,9 +754,9 @@
          sufficient space.-->
     <string name="dvr_error_insufficient_space_description_three_or_more_recordings">The recordings of <xliff:g id="programName_1" example="Friends">%1$s</xliff:g>, <xliff:g id="programName_2" example="Friends">%2$s</xliff:g> and <xliff:g id="programName_3" example="Friends">%3$s</xliff:g> didn\'t complete due to insufficient storage.</string>
     <!-- Dialog title which will be shown when the current storage is too small for DVR. -->
-    <string name="dvr_error_small_sized_storage_title">DVR needs more storage</string>
+    <string name="dvr_error_small_sized_storage_title">More stroage needed</string>
     <!-- Dialog description which will be shown when the current storage is too small for DVR. -->
-    <string name="dvr_error_small_sized_storage_description">You will be able to record programs with DVR. However there is not enough storage on your device now for DVR to work. Please connect an external drive that is <xliff:g id="storage size" example="10GB">%1$d</xliff:g>GB or larger and follow the steps to format it as device storage.</string>
+    <string name="dvr_error_small_sized_storage_description">You will be able to record programs. However there is not enough storage on your device to start recording. Please connect an external drive that is <xliff:g id="storage_size" example="10GB">%1$d</xliff:g>GB or larger and follow the steps to format it as device storage.</string>
     <!-- Dialog title which will be shown when there is no free space on the current storage for DVR. -->
     <string name="dvr_error_no_free_space_title">Not enough storage</string>
     <!-- Dialog description which will be shown when there is no free space on the current storage for DVR. -->
@@ -762,7 +764,7 @@
     <!-- Dialog title which will be shown when the current DVR storage is not accessible. -->
     <string name="dvr_error_missing_storage_title">Missing storage</string>
     <!-- Dialog description which will be shown when the current DVR storage is not accessible. -->
-    <string name="dvr_error_missing_storage_description" translatable="false">Some of the storage used by DVR is missing. Please connect the external drive you used before to re-enable DVR. Alternately, you can forget the storage in the storage settings, if it\'s no longer available.</string>
+    <string name="dvr_error_missing_storage_description" translatable="false">Some of the storage used for recording is missing. Please connect the external drive you used before to re-enable recording. Alternately, you can forget the storage in the storage settings, if it\'s no longer available.</string>
     <!-- The recording being requested to play is not existent in storage. It may be deleted. -->
     <string name="dvr_toast_recording_deleted" translatable="false">The recording seems to be deleted.</string>
 
@@ -892,7 +894,10 @@
     <string name="dvr_schedules_tuner_conflict_will_not_be_recorded_info">Won\'t be recorded due to tuner conflicts.</string>
     <!-- Description of no schedule recording for now, and ask user to schedule recordings from
          the program guide. -->
-    <string name="dvr_schedules_empty_state">There are no recordings on schedule yet.\nYou can schedule recording from the program guide.</string>
+    <string name="dvr_schedules_empty_state">There are no recordings scheduled yet.\nYou can schedule recordings from the program guide.</string>
+    <!-- Description of no recording history for now, and ask user to schedule recordings from
+         the program guide. -->
+    <string name="dvr_history_empty_state">There is no recording history.\nYou can schedule recordings from the program guide.</string>
     <!-- Description of schedule list header about how many recordings conflict. -->
     <plurals name="dvr_series_schedules_header_description">
         <item quantity="one">%1$d recording conflict</item>
diff --git a/res/xml/remote_config_defaults.xml b/res/xml/remote_config_defaults.xml
deleted file mode 100644
index 6ecad19..0000000
--- a/res/xml/remote_config_defaults.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2016 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.
-  -->
-<defaultsMap>
-    <entry>
-        <key>live_channels_ac3_software_decoder</key>
-        <value>false</value>
-    </entry>
-
-    <entry>
-        <key>live_channels_epg_host_and_port</key>
-        <value>datamixer-pa.googleapis.com:443</value>
-    </entry>
-
-    <entry>
-        <key>live_channels_epg_fetcher_interval_hour</key>
-        <value>4</value>
-    </entry>
-
-    <entry>
-        <key>live_channels_epg_reader_max_channels_per_program_fetch</key>
-        <value>20</value>
-    </entry>
-</defaultsMap>
\ No newline at end of file
diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
deleted file mode 100644
index 2b7817d..0000000
--- a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
+++ /dev/null
@@ -1,127 +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.google.android.exoplayer2.ext.ffmpeg;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
-import com.google.android.exoplayer2.util.MimeTypes;
-import com.android.tv.common.SoftPreconditions;
-
-import java.nio.ByteBuffer;
-
-/**
- * Audio decoder which uses ffmpeg extension of ExoPlayer2. Since {@link FfmpegDecoder} is package
- * private, expose the decoder via this class. Supported formats are AC3 and MP2.
- */
-public class FfmpegAudioDecoder {
-    private static final int NUM_DECODER_BUFFERS = 1;
-
-    // The largest AC3 sample size. This is bigger than the largest MP2 sample size (1729).
-    private static final int INITIAL_INPUT_BUFFER_SIZE = 2560;
-    private static boolean AVAILABLE;
-
-    static {
-        AVAILABLE =
-                FfmpegLibrary.supportsFormat(MimeTypes.AUDIO_AC3)
-                        && FfmpegLibrary.supportsFormat(MimeTypes.AUDIO_MPEG_L2);
-    }
-
-    private FfmpegDecoder mDecoder;
-    private DecoderInputBuffer mInputBuffer;
-    private SimpleOutputBuffer mOutputBuffer;
-    private boolean mStarted;
-
-    /** Return whether Ffmpeg based software audio decoder is available. */
-    public static boolean isAvailable() {
-        return AVAILABLE;
-    }
-
-    /** Creates an Ffmpeg based software audio decoder. */
-    public FfmpegAudioDecoder(Context context) {
-        if (context.checkSelfPermission("android.permission.INTERNET")
-                == PackageManager.PERMISSION_GRANTED) {
-            throw new IllegalStateException("This code should run in an isolated process");
-        }
-    }
-
-    /**
-     * Decodes an audio sample.
-     *
-     * @param timeUs presentation timestamp of the sample
-     * @param sample data
-     */
-    public void decode(long timeUs, byte[] sample) {
-        SoftPreconditions.checkState(AVAILABLE);
-        mInputBuffer.data.clear();
-        mInputBuffer.data.put(sample);
-        mInputBuffer.data.flip();
-        mInputBuffer.timeUs = timeUs;
-        mDecoder.decode(mInputBuffer, mOutputBuffer, !mStarted);
-        if (!mStarted) {
-            mStarted = true;
-        }
-    }
-
-    /** Returns a decoded sample from decoder. */
-    public ByteBuffer getDecodedSample() {
-        return mOutputBuffer.data;
-    }
-
-    /** Returns the presentation time for the decoded sample. */
-    public long getDecodedTimeUs() {
-        return mOutputBuffer.timeUs;
-    }
-
-    /**
-     * Clear previous decode state if any. Prepares to decode samples of the specified encoding.
-     * This method should be called before using decode.
-     *
-     * @param mime audio encoding
-     */
-    public void resetDecoderState(String mime) {
-        SoftPreconditions.checkState(AVAILABLE);
-        release();
-        try {
-            mDecoder =
-                    new FfmpegDecoder(
-                            NUM_DECODER_BUFFERS,
-                            NUM_DECODER_BUFFERS,
-                            INITIAL_INPUT_BUFFER_SIZE,
-                            mime,
-                            null);
-            mStarted = false;
-            mInputBuffer = mDecoder.createInputBuffer();
-            // Since native JNI requires direct buffer, we should allocate it by #allocateDirect.
-            mInputBuffer.data = ByteBuffer.allocateDirect(INITIAL_INPUT_BUFFER_SIZE);
-            mOutputBuffer = mDecoder.createOutputBuffer();
-        } catch (FfmpegDecoderException e) {
-            // if AVAILABLE is {@code true}, this will not happen.
-        }
-    }
-
-    /** Releases all the resource. */
-    public void release() {
-        SoftPreconditions.checkState(AVAILABLE);
-        if (mDecoder != null) {
-            mDecoder.release();
-            mInputBuffer = null;
-            mOutputBuffer = null;
-            mDecoder = null;
-        }
-    }
-}
diff --git a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java b/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java
deleted file mode 100644
index daa7734..0000000
--- a/src/com/android/exoplayer2/ext/ffmpeg/FfmpegLibrary.java
+++ /dev/null
@@ -1,84 +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.google.android.exoplayer2.ext.ffmpeg;
-
-import com.google.android.exoplayer2.util.LibraryLoader;
-import com.google.android.exoplayer2.util.MimeTypes;
-
-/**
- * This class is based on com.google.android.exoplayer2.ext.ffmpeg.FfmpegLibrary from ExoPlayer2
- * in order to support mp2 decoder.
- * Configures and queries the underlying native library.
- */
-public final class FfmpegLibrary {
-
-    private static final LibraryLoader LOADER =
-            new LibraryLoader("avutil", "avresample", "avcodec", "ffmpeg");
-
-    private FfmpegLibrary() {}
-
-    /**
-     * Overrides the names of the FFmpeg native libraries. If an application wishes to call this
-     * method, it must do so before calling any other method defined by this class, and before
-     * instantiating a {@link FfmpegAudioRenderer} instance.
-     */
-    public static void setLibraries(String... libraries) {
-        LOADER.setLibraries(libraries);
-    }
-
-    /**
-     * Returns whether the underlying library is available, loading it if necessary.
-     */
-    public static boolean isAvailable() {
-        return LOADER.isAvailable();
-    }
-
-    /**
-     * Returns the version of the underlying library if available, or null otherwise.
-     */
-    public static String getVersion() {
-        return isAvailable() ? ffmpegGetVersion() : null;
-    }
-
-    /**
-     * Returns whether the underlying library supports the specified MIME type.
-     */
-    public static boolean supportsFormat(String mimeType) {
-        if (!isAvailable()) {
-            return false;
-        }
-        String codecName = getCodecName(mimeType);
-        return codecName != null && ffmpegHasDecoder(codecName);
-    }
-
-    /**
-     * Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}.
-     */
-    /* package */ static String getCodecName(String mimeType) {
-        switch (mimeType) {
-            case MimeTypes.AUDIO_MPEG_L2:
-                return "mp2";
-            case MimeTypes.AUDIO_AC3:
-                return "ac3";
-            default:
-                return null;
-        }
-    }
-
-    private static native String ffmpegGetVersion();
-    private static native boolean ffmpegHasDecoder(String codecName);
-
-}
diff --git a/src/com/android/tv/AudioManagerHelper.java b/src/com/android/tv/AudioManagerHelper.java
index 4fca06a..942d431 100644
--- a/src/com/android/tv/AudioManagerHelper.java
+++ b/src/com/android/tv/AudioManagerHelper.java
@@ -1,66 +1,94 @@
+/*
+ * 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;
 
 import android.app.Activity;
 import android.content.Context;
 import android.media.AudioManager;
 import android.os.Build;
-
 import com.android.tv.receiver.AudioCapabilitiesReceiver;
 import com.android.tv.ui.TunableTvView;
+import com.android.tv.ui.TunableTvViewPlayingApi;
 
-/**
- * A helper class to help {@link MainActivity} to handle audio-related stuffs.
- */
+/** A helper class to help {@link MainActivity} to handle audio-related stuffs. */
 class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener {
     private static final float AUDIO_MAX_VOLUME = 1.0f;
     private static final float AUDIO_MIN_VOLUME = 0.0f;
     private static final float AUDIO_DUCKING_VOLUME = 0.3f;
 
     private final Activity mActivity;
-    private final TunableTvView mTvView;
+    private final TunableTvViewPlayingApi mTvView;
     private final AudioManager mAudioManager;
     private final AudioCapabilitiesReceiver mAudioCapabilitiesReceiver;
 
     private boolean mAc3PassthroughSupported;
     private int mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
 
-    AudioManagerHelper(Activity activity, TunableTvView tvView) {
+    AudioManagerHelper(Activity activity, TunableTvViewPlayingApi tvView) {
         mActivity = activity;
         mTvView = tvView;
         mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE);
-        mAudioCapabilitiesReceiver = new AudioCapabilitiesReceiver(activity,
-                new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() {
-                    @Override
-                    public void onAc3PassthroughCapabilityChange(boolean capability) {
-                        mAc3PassthroughSupported = capability;
-                    }
-                });
+        mAudioCapabilitiesReceiver =
+                new AudioCapabilitiesReceiver(
+                        activity,
+                        new AudioCapabilitiesReceiver.OnAc3PassthroughCapabilityChangeListener() {
+                            @Override
+                            public void onAc3PassthroughCapabilityChange(boolean capability) {
+                                mAc3PassthroughSupported = capability;
+                            }
+                        });
         mAudioCapabilitiesReceiver.register();
     }
 
     /**
-     * Sets suitable volume to {@link TunableTvView} according to the current audio focus.
-     * If the focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is under PIP
-     * mode, this method will finish the activity.
+     * Sets suitable volume to {@link TunableTvView} according to the current audio focus. If the
+     * focus status is {@link AudioManager#AUDIOFOCUS_LOSS} and the activity is under PIP mode, this
+     * method will finish the activity.
      */
     void setVolumeByAudioFocusStatus() {
         if (mTvView.isPlaying()) {
             switch (mAudioFocusStatus) {
                 case AudioManager.AUDIOFOCUS_GAIN:
-                    mTvView.setStreamVolume(AUDIO_MAX_VOLUME);
+                    if (mTvView.isTimeShiftAvailable()) {
+                        mTvView.timeshiftPlay();
+                    } else {
+                        mTvView.setStreamVolume(AUDIO_MAX_VOLUME);
+                    }
                     break;
                 case AudioManager.AUDIOFOCUS_LOSS:
-                    if (Features.PICTURE_IN_PICTURE.isEnabled(mActivity)
+                    if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(mActivity)
                             && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                             && mActivity.isInPictureInPictureMode()) {
                         mActivity.finish();
                         break;
                     }
+                    // fall through
                 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
-                    mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+                    if (mTvView.isTimeShiftAvailable()) {
+                        mTvView.timeshiftPause();
+                    } else {
+                        mTvView.setStreamVolume(AUDIO_MIN_VOLUME);
+                    }
                     break;
                 case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
-                    mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
+                    if (mTvView.isTimeShiftAvailable()) {
+                        mTvView.timeshiftPause();
+                    } else {
+                        mTvView.setStreamVolume(AUDIO_DUCKING_VOLUME);
+                    }
                     break;
             }
         }
@@ -71,31 +99,28 @@
      * returned result.
      */
     void requestAudioFocus() {
-        int result = mAudioManager.requestAudioFocus(this,
-                AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
-        mAudioFocusStatus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) ?
-                AudioManager.AUDIOFOCUS_GAIN : AudioManager.AUDIOFOCUS_LOSS;
+        int result =
+                mAudioManager.requestAudioFocus(
+                        this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+        mAudioFocusStatus =
+                (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
+                        ? AudioManager.AUDIOFOCUS_GAIN
+                        : AudioManager.AUDIOFOCUS_LOSS;
         setVolumeByAudioFocusStatus();
     }
 
-    /**
-     * Abandons audio focus.
-     */
+    /** Abandons audio focus. */
     void abandonAudioFocus() {
         mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS;
         mAudioManager.abandonAudioFocus(this);
     }
 
-    /**
-     * Returns {@code true} if the device supports AC3 pass-through.
-     */
+    /** Returns {@code true} if the device supports AC3 pass-through. */
     boolean isAc3PassthroughSupported() {
         return mAc3PassthroughSupported;
     }
 
-    /**
-     * Release the resources the helper class may occupied.
-     */
+    /** Release the resources the helper class may occupied. */
     void release() {
         mAudioCapabilitiesReceiver.unregister();
     }
diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java
index faa27bb..8ab145a 100644
--- a/src/com/android/tv/ChannelTuner.java
+++ b/src/com/android/tv/ChannelTuner.java
@@ -24,12 +24,10 @@
 import android.support.annotation.Nullable;
 import android.util.ArraySet;
 import android.util.Log;
-
 import com.android.tv.common.SoftPreconditions;
-import com.android.tv.data.Channel;
 import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.api.Channel;
 import com.android.tv.util.TvInputManagerHelper;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -57,11 +55,9 @@
     private final Handler mHandler = new Handler();
     private final ChannelDataManager mChannelDataManager;
     private final Set<Listener> mListeners = new ArraySet<>();
-    @Nullable
-    private Channel mCurrentChannel;
+    @Nullable private Channel mCurrentChannel;
     private final TvInputManagerHelper mInputManager;
-    @Nullable
-    private TvInputInfo mCurrentChannelInputInfo;
+    @Nullable private TvInputInfo mCurrentChannelInputInfo;
 
     private final ChannelDataManager.Listener mChannelDataManagerListener =
             new ChannelDataManager.Listener() {
@@ -86,16 +82,14 @@
                         l.onBrowsableChannelListChanged();
                     }
                 }
-    };
+            };
 
     public ChannelTuner(ChannelDataManager channelDataManager, TvInputManagerHelper inputManager) {
         mChannelDataManager = channelDataManager;
         mInputManager = inputManager;
     }
 
-    /**
-     * Starts ChannelTuner. It cannot be called twice before calling {@link #stop}.
-     */
+    /** Starts ChannelTuner. It cannot be called twice before calling {@link #stop}. */
     public void start() {
         if (mStarted) {
             throw new IllegalStateException("start is called twice");
@@ -103,18 +97,17 @@
         mStarted = true;
         mChannelDataManager.addListener(mChannelDataManagerListener);
         if (mChannelDataManager.isDbLoadFinished()) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mChannelDataManagerListener.onLoadFinished();
-                }
-            });
+            mHandler.post(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            mChannelDataManagerListener.onLoadFinished();
+                        }
+                    });
         }
     }
 
-    /**
-     * Stops ChannelTuner.
-     */
+    /** Stops ChannelTuner. */
     public void stop() {
         if (!mStarted) {
             return;
@@ -130,30 +123,22 @@
         mChannelDataManagerLoaded = false;
     }
 
-    /**
-     * Returns true, if all the channels are loaded.
-     */
+    /** Returns true, if all the channels are loaded. */
     public boolean areAllChannelsLoaded() {
         return mChannelDataManagerLoaded;
     }
 
-    /**
-     * Returns browsable channel lists.
-     */
+    /** Returns browsable channel lists. */
     public List<Channel> getBrowsableChannelList() {
         return Collections.unmodifiableList(mBrowsableChannels);
     }
 
-    /**
-     * Returns the number of browsable channels.
-     */
+    /** Returns the number of browsable channels. */
     public int getBrowsableChannelCount() {
         return mBrowsableChannels.size();
     }
 
-    /**
-     * Returns the current channel.
-     */
+    /** Returns the current channel. */
     @Nullable
     public Channel getCurrentChannel() {
         return mCurrentChannel;
@@ -169,16 +154,12 @@
         mCurrentChannel = currentChannel;
     }
 
-    /**
-     * Returns the current channel's ID.
-     */
+    /** Returns the current channel's ID. */
     public long getCurrentChannelId() {
         return mCurrentChannel != null ? mCurrentChannel.getId() : Channel.INVALID_ID;
     }
 
-    /**
-     * Returns the current channel's URI
-     */
+    /** Returns the current channel's URI */
     public Uri getCurrentChannelUri() {
         if (mCurrentChannel == null) {
             return null;
@@ -190,17 +171,13 @@
         }
     }
 
-    /**
-     * Returns the current {@link TvInputInfo}.
-     */
+    /** Returns the current {@link TvInputInfo}. */
     @Nullable
     public TvInputInfo getCurrentInputInfo() {
         return mCurrentChannelInputInfo;
     }
 
-    /**
-     * Returns true, if the current channel is for a passthrough TV input.
-     */
+    /** Returns true, if the current channel is for a passthrough TV input. */
     public boolean isCurrentChannelPassthrough() {
         return mCurrentChannel != null && mCurrentChannel.isPassthrough();
     }
@@ -208,8 +185,8 @@
     /**
      * Moves the current channel to the next (or previous) browsable channel.
      *
-     * @return true, if the channel is changed to the adjacent channel. If there is no
-     *         browsable channel, it returns false.
+     * @return true, if the channel is changed to the adjacent channel. If there is no browsable
+     *     channel, it returns false.
      */
     public boolean moveToAdjacentBrowsableChannel(boolean up) {
         Channel channel = getAdjacentBrowsableChannel(up);
@@ -221,8 +198,8 @@
     }
 
     /**
-     * Returns a next browsable channel. It doesn't change the current channel unlike
-     * {@link #moveToAdjacentBrowsableChannel}.
+     * Returns a next browsable channel. It doesn't change the current channel unlike {@link
+     * #moveToAdjacentBrowsableChannel}.
      */
     public Channel getAdjacentBrowsableChannel(boolean up) {
         if (isCurrentChannelPassthrough() || getBrowsableChannelCount() == 0) {
@@ -240,8 +217,7 @@
         }
         int size = mChannels.size();
         for (int i = 0; i < size; ++i) {
-            int nextChannelIndex = up ? channelIndex + 1 + i
-                    : channelIndex - 1 - i + size;
+            int nextChannelIndex = up ? channelIndex + 1 + i : channelIndex - 1 - i + size;
             if (nextChannelIndex >= size) {
                 nextChannelIndex -= size;
             }
@@ -289,7 +265,7 @@
      * as a browsable channel.
      *
      * @return true, the channel change is success. But, if the channel doesn't exist, the channel
-     *         change will be failed and it will return false.
+     *     change will be failed and it will return false.
      */
     public boolean moveToChannel(Channel channel) {
         if (channel == null) {
@@ -308,43 +284,29 @@
         return false;
     }
 
-    /**
-     * Resets the current channel to {@code null}.
-     */
+    /** Resets the current channel to {@code null}. */
     public void resetCurrentChannel() {
         setCurrentChannelAndNotify(null);
     }
 
-    /**
-     * Adds {@link Listener}.
-     */
+    /** Adds {@link Listener}. */
     public void addListener(Listener listener) {
         mListeners.add(listener);
     }
 
-    /**
-     * Removes {@link Listener}.
-     */
+    /** Removes {@link Listener}. */
     public void removeListener(Listener listener) {
         mListeners.remove(listener);
     }
 
     public interface Listener {
-        /**
-         * Called when all the channels are loaded.
-         */
+        /** Called when all the channels are loaded. */
         void onLoadFinished();
-        /**
-         * Called when the browsable channel list is changed.
-         */
+        /** Called when the browsable channel list is changed. */
         void onBrowsableChannelListChanged();
-        /**
-         * Called when the current channel is removed.
-         */
+        /** Called when the current channel is removed. */
         void onCurrentChannelUnavailable(Channel channel);
-        /**
-         * Called when the current channel is changed.
-         */
+        /** Called when the current channel is changed. */
         void onChannelChanged(Channel previousChannel, Channel currentChannel);
     }
 
diff --git a/src/com/android/tv/Features.java b/src/com/android/tv/Features.java
deleted file mode 100644
index 2052f2e..0000000
--- a/src/com/android/tv/Features.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2015 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;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.tv.common.feature.Feature;
-import com.android.tv.common.feature.GServiceFeature;
-import com.android.tv.common.feature.PropertyFeature;
-import com.android.tv.config.RemoteConfig;
-import com.android.tv.experiments.Experiments;
-import com.android.tv.util.LocationUtils;
-import com.android.tv.util.PermissionUtils;
-import com.android.tv.util.Utils;
-
-import java.util.Locale;
-
-import static com.android.tv.common.feature.EngOnlyFeature.ENG_ONLY_FEATURE;
-import static com.android.tv.common.feature.FeatureUtils.AND;
-import static com.android.tv.common.feature.FeatureUtils.OFF;
-import static com.android.tv.common.feature.FeatureUtils.ON;
-import static com.android.tv.common.feature.FeatureUtils.OR;
-
-/**
- * List of {@link Feature} for the Live TV App.
- *
- * <p>Remove the {@code Feature} once it is launched.
- */
-public final class Features {
-    private static final String TAG = "Features";
-    private static final boolean DEBUG = false;
-
-    /**
-     * UI for opting in to analytics.
-     *
-     * <p>Do not turn this on until the splash screen asking existing users to opt-in is launched.
-     * See <a href="http://b/20228119">b/20228119</a>
-     */
-    public static final Feature ANALYTICS_OPT_IN = ENG_ONLY_FEATURE;
-
-    /**
-     * Analytics that include sensitive information such as channel or program identifiers.
-     *
-     * <p>See <a href="http://b/22062676">b/22062676</a>
-     */
-    public static final Feature ANALYTICS_V2 = AND(ON, ANALYTICS_OPT_IN);
-
-    public static final Feature EPG_SEARCH =
-            new PropertyFeature("feature_tv_use_epg_search", false);
-
-    public static final Feature TUNER =
-            new Feature() {
-                @Override
-                public boolean isEnabled(Context context) {
-
-                    if (Utils.isDeveloper()) {
-                        // we enable tuner for developers to test tuner in any platform.
-                        return true;
-                    }
-
-                    // This is special handling just for USB Tuner.
-                    // It does not require any N API's but relies on a improvements in N for AC3 support
-                    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
-                }
-            };
-
-    /**
-     * Use network tuner if it is available and there is no other tuner types.
-     */
-    public static final Feature NETWORK_TUNER =
-            new Feature() {
-                @Override
-                public boolean isEnabled(Context context) {
-                    if (!TUNER.isEnabled(context)) {
-                        return false;
-                    }
-                    if (Utils.isDeveloper()) {
-                        // Network tuner will be enabled for developers.
-                        return true;
-                    }
-                    return Locale.US.getCountry().equalsIgnoreCase(
-                            LocationUtils.getCurrentCountry(context));
-                }
-            };
-
-    private static final String GSERVICE_KEY_UNHIDE = "live_channels_unhide";
-    /**
-     * A flag which indicates that LC app is unhidden even when there is no input.
-     */
-    public static final Feature UNHIDE =
-            OR(new GServiceFeature(GSERVICE_KEY_UNHIDE, false), new Feature() {
-                @Override
-                public boolean isEnabled(Context context) {
-                    // If LC app runs as non-system app, we unhide the app.
-                    return !PermissionUtils.hasAccessAllEpg(context);
-                }
-            });
-
-    public static final Feature PICTURE_IN_PICTURE =
-            new Feature() {
-                private Boolean mEnabled;
-
-                @Override
-                public boolean isEnabled(Context context) {
-                    if (mEnabled == null) {
-                        mEnabled =
-                                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
-                                        && context.getPackageManager()
-                                                .hasSystemFeature(
-                                                        PackageManager.FEATURE_PICTURE_IN_PICTURE);
-                    }
-                    return mEnabled;
-                }
-            };
-
-    /** Use AC3 software decode. */
-    public static final Feature AC3_SOFTWARE_DECODE =
-            new Feature() {
-                private final String[] SUPPORTED_REGIONS = {};
-
-                private Boolean mEnabled;
-
-                @Override
-                public boolean isEnabled(Context context) {
-                    if (mEnabled == null) {
-                        if (mEnabled == null) {
-                            // We will not cache the result of fallback solution.
-                            String country = LocationUtils.getCurrentCountry(context);
-                            for (int i = 0; i < SUPPORTED_REGIONS.length; ++i) {
-                                if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) {
-                                    return true;
-                                }
-                            }
-                            if (DEBUG) Log.d(TAG, "AC3 flag false after country check");
-                            return false;
-                        }
-                    }
-                    if (DEBUG) Log.d(TAG, "AC3 flag " + mEnabled);
-                    return mEnabled;
-                }
-            };
-
-    /** Show postal code fragment before channel scan. */
-    public static final Feature ENABLE_CLOUD_EPG_REGION =
-            new Feature() {
-                private final String[] SUPPORTED_REGIONS = {
-                };
-
-
-                @Override
-                public boolean isEnabled(Context context) {
-                    if (!Experiments.CLOUD_EPG.get()) {
-                        if (DEBUG) Log.d(TAG, "Experiments.CLOUD_EPG is false");
-                        return false;
-                    }
-                    String country = LocationUtils.getCurrentCountry(context);
-                    for (int i = 0; i < SUPPORTED_REGIONS.length; i++) {
-                        if (SUPPORTED_REGIONS[i].equalsIgnoreCase(country)) {
-                            return true;
-                        }
-                    }
-                    if (DEBUG) Log.d(TAG, "EPG flag false after country check");
-                    return false;
-                }
-            };
-
-    /** 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;
-
-    /**
-     * Enable Dvb parsers and listeners.
-     */
-    public static final Feature ENABLE_FILE_DVB = OFF;
-
-    @VisibleForTesting
-    public static final Feature TEST_FEATURE = new PropertyFeature("test_feature", false);
-
-    private Features() {
-    }
-}
diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java
index 2978f40..4f298ed 100644
--- a/src/com/android/tv/InputSessionManager.java
+++ b/src/com/android/tv/InputSessionManager.java
@@ -36,28 +36,27 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
-
-import com.android.tv.data.Channel;
+import com.android.tv.data.api.Channel;
 import com.android.tv.ui.TunableTvView;
 import com.android.tv.ui.TunableTvView.OnTuneListener;
 import com.android.tv.util.TvInputManagerHelper;
-
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 
 /**
- * Manages input sessions.
- * Responsible for:
+ * Manages input sessions. Responsible for:
+ *
  * <ul>
- *     <li>Manage {@link TvView} sessions and recording sessions</li>
- *     <li>Manage capabilities (conflict)</li>
+ *   <li>Manage {@link TvView} sessions and recording sessions
+ *   <li>Manage capabilities (conflict)
  * </ul>
- * <p>
- * As TvView's methods should be called on the main thread and the {@link RecordingSession} should
- * look at the state of the {@link TvViewSession} when it calls the framework methods, the framework
- * calls in RecordingSession are made on the main thread not to introduce the multi-thread problems.
+ *
+ * <p>As TvView's methods should be called on the main thread and the {@link RecordingSession}
+ * should look at the state of the {@link TvViewSession} when it calls the framework methods, the
+ * framework calls in RecordingSession are made on the main thread not to introduce the multi-thread
+ * problems.
  */
 @TargetApi(Build.VERSION_CODES.N)
 public class InputSessionManager {
@@ -77,27 +76,25 @@
 
     public InputSessionManager(Context context) {
         mContext = context.getApplicationContext();
-        mInputManager = TvApplication.getSingletons(context).getTvInputManagerHelper();
+        mInputManager = TvSingletons.getSingletons(context).getTvInputManagerHelper();
     }
 
     /**
      * Creates the session for {@link TvView}.
-     * <p>
-     * Do not call {@link TvView#setCallback} after the session is created.
+     *
+     * <p>Do not call {@link TvView#setCallback} after the session is created.
      */
     @MainThread
     @NonNull
-    public TvViewSession createTvViewSession(TvView tvView, TunableTvView tunableTvView,
-            TvInputCallback callback) {
+    public TvViewSession createTvViewSession(
+            TvView tvView, TunableTvView tunableTvView, TvInputCallback callback) {
         TvViewSession session = new TvViewSession(tvView, tunableTvView, callback);
         mTvViewSessions.add(session);
         if (DEBUG) Log.d(TAG, "TvView session created: " + session);
         return session;
     }
 
-    /**
-     * Releases the {@link TvView} session.
-     */
+    /** Releases the {@link TvView} session. */
     @MainThread
     public void releaseTvViewSession(TvViewSession session) {
         mTvViewSessions.remove(session);
@@ -105,12 +102,14 @@
         if (DEBUG) Log.d(TAG, "TvView session released: " + session);
     }
 
-    /**
-     * Creates the session for recording.
-     */
+    /** Creates the session for recording. */
     @NonNull
-    public RecordingSession createRecordingSession(String inputId, String tag,
-            RecordingCallback callback, Handler handler, long endTimeMs) {
+    public RecordingSession createRecordingSession(
+            String inputId,
+            String tag,
+            RecordingCallback callback,
+            Handler handler,
+            long endTimeMs) {
         RecordingSession session = new RecordingSession(inputId, tag, callback, handler, endTimeMs);
         mRecordingSessions.add(session);
         if (DEBUG) Log.d(TAG, "Recording session created: " + session);
@@ -120,9 +119,7 @@
         return session;
     }
 
-    /**
-     * Releases the recording session.
-     */
+    /** Releases the recording session. */
     public void releaseRecordingSession(RecordingSession session) {
         mRecordingSessions.remove(session);
         session.release();
@@ -132,17 +129,13 @@
         }
     }
 
-    /**
-     * Adds the {@link OnTvViewChannelChangeListener}.
-     */
+    /** Adds the {@link OnTvViewChannelChangeListener}. */
     @MainThread
     public void addOnTvViewChannelChangeListener(OnTvViewChannelChangeListener listener) {
         mOnTvViewChannelChangeListeners.add(listener);
     }
 
-    /**
-     * Removes the {@link OnTvViewChannelChangeListener}.
-     */
+    /** Removes the {@link OnTvViewChannelChangeListener}. */
     @MainThread
     public void removeOnTvViewChannelChangeListener(OnTvViewChannelChangeListener listener) {
         mOnTvViewChannelChangeListeners.remove(listener);
@@ -176,9 +169,7 @@
         return null;
     }
 
-    /**
-     * Retruns the earliest end time of recording sessions in progress of the certain TV input.
-     */
+    /** Retruns the earliest end time of recording sessions in progress of the certain TV input. */
     @MainThread
     public Long getEarliestRecordingSessionEndTimeMs(String inputId) {
         long timeMs = Long.MAX_VALUE;
@@ -240,8 +231,8 @@
 
     /**
      * The session for {@link TvView}.
-     * <p>
-     * The methods which create or release session for the TV input should be called through this
+     *
+     * <p>The methods which create or release session for the TV input should be called through this
      * session.
      */
     @MainThread
@@ -261,31 +252,32 @@
             mTvView = tvView;
             mTunableTvView = tunableTvView;
             mCallback = callback;
-            mTvView.setCallback(new DelegateTvInputCallback(mCallback) {
-                @Override
-                public void onConnectionFailed(String inputId) {
-                    if (DEBUG) Log.d(TAG, "TvViewSession: connection failed");
-                    mTuned = false;
-                    mNeedToBeRetuned = false;
-                    super.onConnectionFailed(inputId);
-                    notifyTvViewChannelChange(null);
-                }
+            mTvView.setCallback(
+                    new DelegateTvInputCallback(mCallback) {
+                        @Override
+                        public void onConnectionFailed(String inputId) {
+                            if (DEBUG) Log.d(TAG, "TvViewSession: connection failed");
+                            mTuned = false;
+                            mNeedToBeRetuned = false;
+                            super.onConnectionFailed(inputId);
+                            notifyTvViewChannelChange(null);
+                        }
 
-                @Override
-                public void onDisconnected(String inputId) {
-                    if (DEBUG) Log.d(TAG, "TvViewSession: disconnected");
-                    mTuned = false;
-                    mNeedToBeRetuned = false;
-                    super.onDisconnected(inputId);
-                    notifyTvViewChannelChange(null);
-                }
-            });
+                        @Override
+                        public void onDisconnected(String inputId) {
+                            if (DEBUG) Log.d(TAG, "TvViewSession: disconnected");
+                            mTuned = false;
+                            mNeedToBeRetuned = false;
+                            super.onDisconnected(inputId);
+                            notifyTvViewChannelChange(null);
+                        }
+                    });
         }
 
         /**
          * Tunes to the channel.
-         * <p>
-         * As this is called only for the warming up, there's no need to be retuned.
+         *
+         * <p>As this is called only for the warming up, there's no need to be retuned.
          */
         public void tune(String inputId, Uri channelUri) {
             if (DEBUG) {
@@ -299,13 +291,22 @@
             notifyTvViewChannelChange(channelUri);
         }
 
-        /**
-         * Tunes to the channel.
-         */
+        /** Tunes to the channel. */
         public void tune(Channel channel, Bundle params, OnTuneListener listener) {
             if (DEBUG) {
-                Log.d(TAG, "tune: {session=" + this + ", channel=" + channel + ", params=" + params
-                        + ", listener=" + listener + ", mTuned=" + mTuned + "}");
+                Log.d(
+                        TAG,
+                        "tune: {session="
+                                + this
+                                + ", channel="
+                                + channel
+                                + ", params="
+                                + params
+                                + ", listener="
+                                + listener
+                                + ", mTuned="
+                                + mTuned
+                                + "}");
             }
             mChannel = channel;
             mInputId = channel.getInputId();
@@ -313,8 +314,10 @@
             mParams = params;
             mOnTuneListener = listener;
             TvInputInfo input = mInputManager.getTvInputInfo(mInputId);
-            if (input == null || (input.canRecord() && !isTunedForRecording(mChannelUri)
-                    && getTunedRecordingSessionCount(mInputId) >= input.getTunerCount())) {
+            if (input == null
+                    || (input.canRecord()
+                            && !isTunedForRecording(mChannelUri)
+                            && getTunedRecordingSessionCount(mInputId) >= input.getTunerCount())) {
                 if (DEBUG) {
                     if (input == null) {
                         Log.d(TAG, "Can't find input for input ID: " + mInputId);
@@ -354,9 +357,7 @@
             notifyTvViewChannelChange(null);
         }
 
-        /**
-         * Resets this TvView.
-         */
+        /** Resets this TvView. */
         public void reset() {
             if (DEBUG) Log.d(TAG, "Reset TvView session");
             mTuned = false;
@@ -366,8 +367,8 @@
         }
 
         void resetByRecording() {
-            mCallback.onVideoUnavailable(mInputId,
-                    TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE);
+            mCallback.onVideoUnavailable(
+                    mInputId, TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE);
             if (mTuned) {
                 if (DEBUG) Log.d(TAG, "Reset TvView session by recording");
                 mTunableTvView.resetByRecording();
@@ -379,8 +380,8 @@
 
     /**
      * The session for recording.
-     * <p>
-     * The caller is responsible for releasing the session when the error occurs.
+     *
+     * <p>The caller is responsible for releasing the session when the error occurs.
      */
     public class RecordingSession {
         private final String mInputId;
@@ -391,8 +392,12 @@
         private TvRecordingClient mClient;
         private boolean mTuned;
 
-        RecordingSession(String inputId, String tag, RecordingCallback callback,
-                Handler handler, long endTimeMs) {
+        RecordingSession(
+                String inputId,
+                String tag,
+                RecordingCallback callback,
+                Handler handler,
+                long endTimeMs) {
             mInputId = inputId;
             mCallback = callback;
             mHandler = handler;
@@ -402,83 +407,90 @@
 
         void release() {
             if (DEBUG) Log.d(TAG, "Release of recording session requested.");
-            runOnHandler(mMainThreadHandler, new Runnable() {
-                @Override
-                public void run() {
-                    if (DEBUG) Log.d(TAG, "Releasing of recording session.");
-                    mTuned = false;
-                    mClient.release();
-                    mClient = null;
-                    for (TvViewSession session : mTvViewSessions) {
-                        if (DEBUG) {
-                            Log.d(TAG, "Finding TvView sessions for retune: {tuned="
-                                    + session.mTuned + ", inputId=" + session.mInputId
-                                    + ", session=" + session + "}");
+            runOnHandler(
+                    mMainThreadHandler,
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            if (DEBUG) Log.d(TAG, "Releasing of recording session.");
+                            mTuned = false;
+                            mClient.release();
+                            mClient = null;
+                            for (TvViewSession session : mTvViewSessions) {
+                                if (DEBUG) {
+                                    Log.d(
+                                            TAG,
+                                            "Finding TvView sessions for retune: {tuned="
+                                                    + session.mTuned
+                                                    + ", inputId="
+                                                    + session.mInputId
+                                                    + ", session="
+                                                    + session
+                                                    + "}");
+                                }
+                                if (!session.mTuned && Objects.equals(session.mInputId, mInputId)) {
+                                    session.retune();
+                                    break;
+                                }
+                            }
                         }
-                        if (!session.mTuned && Objects.equals(session.mInputId, mInputId)) {
-                            session.retune();
-                            break;
-                        }
-                    }
-                }
-            });
+                    });
         }
 
-        /**
-         * Tunes to the channel for recording.
-         */
+        /** Tunes to the channel for recording. */
         public void tune(String inputId, Uri channelUri) {
-            runOnHandler(mMainThreadHandler, new Runnable() {
-                @Override
-                public void run() {
-                    int tunedRecordingSessionCount = getTunedRecordingSessionCount(inputId);
-                    TvInputInfo input = mInputManager.getTvInputInfo(inputId);
-                    if (input == null || !input.canRecord()
-                            || input.getTunerCount() <= tunedRecordingSessionCount) {
-                        runOnHandler(mHandler, new Runnable() {
-                            @Override
-                            public void run() {
-                                mCallback.onConnectionFailed(inputId);
+            runOnHandler(
+                    mMainThreadHandler,
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            int tunedRecordingSessionCount = getTunedRecordingSessionCount(inputId);
+                            TvInputInfo input = mInputManager.getTvInputInfo(inputId);
+                            if (input == null
+                                    || !input.canRecord()
+                                    || input.getTunerCount() <= tunedRecordingSessionCount) {
+                                runOnHandler(
+                                        mHandler,
+                                        new Runnable() {
+                                            @Override
+                                            public void run() {
+                                                mCallback.onConnectionFailed(inputId);
+                                            }
+                                        });
+                                return;
                             }
-                        });
-                        return;
-                    }
-                    mTuned = true;
-                    int tunedTuneSessionCount = getTunedTvViewSessionCount(inputId);
-                    if (!isTunedForTvView(channelUri) && tunedTuneSessionCount > 0
-                            && tunedRecordingSessionCount + tunedTuneSessionCount
-                                    >= input.getTunerCount()) {
-                        for (TvViewSession session : mTvViewSessions) {
-                            if (session.mTuned && Objects.equals(session.mInputId, inputId)
-                                    && !isTunedForRecording(session.mChannelUri)) {
-                                session.resetByRecording();
-                                break;
+                            mTuned = true;
+                            int tunedTuneSessionCount = getTunedTvViewSessionCount(inputId);
+                            if (!isTunedForTvView(channelUri)
+                                    && tunedTuneSessionCount > 0
+                                    && tunedRecordingSessionCount + tunedTuneSessionCount
+                                            >= input.getTunerCount()) {
+                                for (TvViewSession session : mTvViewSessions) {
+                                    if (session.mTuned
+                                            && Objects.equals(session.mInputId, inputId)
+                                            && !isTunedForRecording(session.mChannelUri)) {
+                                        session.resetByRecording();
+                                        break;
+                                    }
+                                }
                             }
+                            mChannelUri = channelUri;
+                            mClient.tune(inputId, channelUri);
                         }
-                    }
-                    mChannelUri = channelUri;
-                    mClient.tune(inputId, channelUri);
-                }
-            });
+                    });
         }
 
-        /**
-         * Starts recording.
-         */
+        /** Starts recording. */
         public void startRecording(Uri programHintUri) {
             mClient.startRecording(programHintUri);
         }
 
-        /**
-         * Stops recording.
-         */
+        /** Stops recording. */
         public void stopRecording() {
             mClient.stopRecording();
         }
 
-        /**
-         * Sets recording session's ending time.
-         */
+        /** Sets recording session's ending time. */
         public void setEndTimeMs(long endTimeMs) {
             mEndTimeMs = endTimeMs;
         }
@@ -555,9 +567,7 @@
         }
     }
 
-    /**
-     * Called when the {@link TvView} channel is changed.
-     */
+    /** Called when the {@link TvView} channel is changed. */
     public interface OnTvViewChannelChangeListener {
         void onTvViewChannelChange(@Nullable Uri channelUri);
     }
diff --git a/src/com/android/tv/LauncherActivity.java b/src/com/android/tv/LauncherActivity.java
index e03952d..679d612 100644
--- a/src/com/android/tv/LauncherActivity.java
+++ b/src/com/android/tv/LauncherActivity.java
@@ -26,8 +26,8 @@
 /**
  * An activity to launch a new activity.
  *
- * <p>In the case when {@link MainActivity} starts a new activity using
- * {@link Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is
+ * <p>In the case when {@link MainActivity} starts a new activity using {@link
+ * Activity#startActivity} or {@link Activity#startActivityForResult}, Live TV app is
  * terminated if the new activity crashes. That's because the {@link android.app.ActivityManager}
  * terminates the activity which is just below the crashed activity in the activity stack. To avoid
  * this, we need to locate an additional activity between these activities in the activity stack.
@@ -35,8 +35,7 @@
 public class LauncherActivity extends Activity {
     private static final String TAG = "LauncherActivity";
 
-    public static final String ERROR_MESSAGE
-            = "com.android.tv.LauncherActivity.ErrorMessage";
+    public static final String ERROR_MESSAGE = "com.android.tv.LauncherActivity.ErrorMessage";
 
     private static final int REQUEST_CODE_DEFAULT = 0;
     private static final int REQUEST_START_ACTIVITY = 100;
@@ -45,34 +44,16 @@
     private static final String EXTRA_REQUEST_RESULT =
             "com.android.tv.LauncherActivity.REQUEST_RESULT";
 
-    /**
-     * Starts an activity by calling {@link Activity#startActivity}.
-     */
+    /** Starts an activity by calling {@link Activity#startActivity}. */
     public static void startActivitySafe(Activity baseActivity, Intent intentToLaunch) {
         // To avoid the app termination when the new activity crashes, LauncherActivity should be
         // started by calling startActivityForResult().
-        baseActivity.startActivityForResult(createIntent(baseActivity, intentToLaunch, false),
-                REQUEST_CODE_DEFAULT);
+        baseActivity.startActivityForResult(
+                createIntent(baseActivity, intentToLaunch, false), REQUEST_CODE_DEFAULT);
     }
 
-    /**
-     * Starts an activity by calling {@link Activity#startActivityForResult}.
-     *
-     * <p>Note: {@code requestCode} should not be 0. The value is reserved for internal use.
-     */
-    public static void startActivityForResultSafe(Activity baseActivity, Intent intentToLaunch,
-            int requestCode) {
-        if (requestCode == REQUEST_CODE_DEFAULT) {
-            throw new IllegalArgumentException("requestCode should not be 0.");
-        }
-        // To avoid the app termination when the new activity crashes, LauncherActivity should be
-        // started by calling startActivityForResult().
-        baseActivity.startActivityForResult(createIntent(baseActivity, intentToLaunch, true),
-                requestCode);
-    }
-
-    private static Intent createIntent(Context context, Intent intentToLaunch,
-            boolean requestResult) {
+    private static Intent createIntent(
+            Context context, Intent intentToLaunch, boolean requestResult) {
         Intent intent = new Intent(context, LauncherActivity.class);
         intent.putExtra(EXTRA_INTENT, intentToLaunch);
         if (requestResult) {
@@ -98,8 +79,7 @@
             }
         } catch (ActivityNotFoundException e) {
             Log.w(TAG, "Activity not found for " + intent);
-            intent.putExtra(ERROR_MESSAGE,
-                    getResources().getString(R.string.msg_missing_app));
+            intent.putExtra(ERROR_MESSAGE, getResources().getString(R.string.msg_missing_app));
             setResult(Activity.RESULT_CANCELED, intent);
             finish();
         }
diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java
index ed5f79a..94a86cc 100644
--- a/src/com/android/tv/MainActivity.java
+++ b/src/com/android/tv/MainActivity.java
@@ -17,9 +17,12 @@
 package com.android.tv;
 
 import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.SearchManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
@@ -62,26 +65,31 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.Toast;
-
 import com.android.tv.analytics.SendChannelStatusRunnable;
 import com.android.tv.analytics.SendConfigInfoRunnable;
 import com.android.tv.analytics.Tracker;
 import com.android.tv.common.BuildConfig;
-import com.android.tv.common.MemoryManageable;
+import com.android.tv.common.CommonPreferences;
 import com.android.tv.common.SoftPreconditions;
-import com.android.tv.common.TvCommonUtils;
 import com.android.tv.common.TvContentRatingCache;
 import com.android.tv.common.WeakHandler;
 import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.memory.MemoryManageable;
 import com.android.tv.common.ui.setup.OnActionClickListener;
-import com.android.tv.data.Channel;
+import com.android.tv.common.util.CommonUtils;
+import com.android.tv.common.util.ContentUriUtils;
+import com.android.tv.common.util.Debug;
+import com.android.tv.common.util.DurationTimer;
+import com.android.tv.common.util.PermissionUtils;
+import com.android.tv.common.util.SystemProperties;
 import com.android.tv.data.ChannelDataManager;
+import com.android.tv.data.ChannelImpl;
 import com.android.tv.data.OnCurrentProgramUpdatedListener;
 import com.android.tv.data.Program;
 import com.android.tv.data.ProgramDataManager;
 import com.android.tv.data.StreamInfo;
 import com.android.tv.data.WatchedHistoryManager;
-import com.android.tv.data.epg.EpgFetcher;
+import com.android.tv.data.api.Channel;
 import com.android.tv.dialog.HalfSizedDialogFragment;
 import com.android.tv.dialog.PinDialogFragment;
 import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener;
@@ -97,15 +105,10 @@
 import com.android.tv.parental.ParentalControlSettings;
 import com.android.tv.perf.EventNames;
 import com.android.tv.perf.PerformanceMonitor;
-import com.android.tv.perf.StubPerformanceMonitor;
 import com.android.tv.perf.TimerEvent;
 import com.android.tv.recommendation.ChannelPreviewUpdater;
 import com.android.tv.recommendation.NotificationService;
 import com.android.tv.search.ProgramGuideSearchFragment;
-import com.android.tv.tuner.TunerInputController;
-import com.android.tv.tuner.TunerPreferences;
-import com.android.tv.tuner.setup.TunerSetupActivity;
-import com.android.tv.tuner.tvinput.TunerTvInputService;
 import com.android.tv.ui.ChannelBannerView;
 import com.android.tv.ui.InputBannerView;
 import com.android.tv.ui.KeypadChannelSwitchView;
@@ -124,21 +127,18 @@
 import com.android.tv.ui.sidepanel.SettingsFragment;
 import com.android.tv.ui.sidepanel.SideFragment;
 import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment;
-import com.android.tv.util.AccountHelper;
+import com.android.tv.util.AsyncDbTask;
 import com.android.tv.util.CaptionSettings;
-import com.android.tv.util.Debug;
-import com.android.tv.util.DurationTimer;
-import com.android.tv.util.ImageCache;
 import com.android.tv.util.OnboardingUtils;
-import com.android.tv.util.PermissionUtils;
 import com.android.tv.util.RecurringRunner;
 import com.android.tv.util.SetupUtils;
-import com.android.tv.util.SystemProperties;
 import com.android.tv.util.TvInputManagerHelper;
 import com.android.tv.util.TvSettings;
 import com.android.tv.util.TvTrackInfoUtils;
 import com.android.tv.util.Utils;
 import com.android.tv.util.ViewCache;
+import com.android.tv.util.account.AccountHelper;
+import com.android.tv.util.images.ImageCache;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -148,19 +148,23 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
-/**
- * The main activity for the Live TV app.
- */
+/** The main activity for the Live TV app. */
 public class MainActivity extends Activity implements OnActionClickListener, OnPinCheckedListener {
     private static final String TAG = "MainActivity";
     private static final boolean DEBUG = false;
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({KEY_EVENT_HANDLER_RESULT_PASSTHROUGH, KEY_EVENT_HANDLER_RESULT_NOT_HANDLED,
-        KEY_EVENT_HANDLER_RESULT_HANDLED, KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY})
+    @IntDef({
+        KEY_EVENT_HANDLER_RESULT_PASSTHROUGH,
+        KEY_EVENT_HANDLER_RESULT_NOT_HANDLED,
+        KEY_EVENT_HANDLER_RESULT_HANDLED,
+        KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY
+    })
     public @interface KeyHandlerResultType {}
+
     public static final int KEY_EVENT_HANDLER_RESULT_PASSTHROUGH = 0;
     public static final int KEY_EVENT_HANDLER_RESULT_NOT_HANDLED = 1;
     public static final int KEY_EVENT_HANDLER_RESULT_HANDLED = 2;
@@ -171,12 +175,12 @@
     private static final float FRAME_RATE_FOR_FILM = 23.976f;
     private static final float FRAME_RATE_EPSILON = 0.1f;
 
-
     private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
     private static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS";
 
     // Tracker screen names.
     public static final String SCREEN_NAME = "Main";
+    private static final String SCREEN_PIP = "PIP";
     private static final String SCREEN_BEHIND_NAME = "Behind";
 
     private static final float REFRESH_RATE_EPSILON = 0.01f;
@@ -196,8 +200,8 @@
         BLACKLIST_KEYCODE_TO_TIS.add(KeyEvent.KEYCODE_WINDOW);
     }
 
-
     private static final IntentFilter SYSTEM_INTENT_FILTER = new IntentFilter();
+
     static {
         SYSTEM_INTENT_FILTER.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
         SYSTEM_INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
@@ -206,6 +210,7 @@
     }
 
     private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
+    private static final int REQUEST_CODE_NOW_PLAYING = 2;
 
     private static final String KEY_INIT_CHANNEL_ID = "com.android.tv.init_channel_id";
 
@@ -240,14 +245,13 @@
     private final DurationTimer mTuneDurationTimer = new DurationTimer();
     private DvrManager mDvrManager;
     private ConflictChecker mDvrConflictChecker;
+    private SetupUtils mSetupUtils;
 
     private View mContentView;
     private TunableTvView mTvView;
     private Bundle mTuneParams;
-    @Nullable
-    private Uri mInitChannelUri;
-    @Nullable
-    private String mParentInputIdWhenScreenOff;
+    @Nullable private Uri mInitChannelUri;
+    @Nullable private String mParentInputIdWhenScreenOff;
     private boolean mScreenOffIntentReceived;
     private boolean mShowProgramGuide;
     private boolean mShowSelectInputView;
@@ -274,6 +278,7 @@
     private boolean mOtherActivityLaunched;
     private PerformanceMonitor mPerformanceMonitor;
 
+    private boolean mIsInPIPMode;
     private boolean mIsFilmModeSet;
     private float mDefaultRefreshRate;
 
@@ -302,69 +307,74 @@
     private final Handler mHandler = new MainActivityHandler(this);
     private final Set<OnActionClickListener> mOnActionClickListeners = new ArraySet<>();
 
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case Intent.ACTION_SCREEN_OFF:
-                    if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF");
-                    // We need to stop TvView, when the screen is turned off. If not and TIS uses
-                    // MediaPlayer, a device may not go to the sleep mode and audio can be heard,
-                    // because MediaPlayer keeps playing media by its wake lock.
-                    mScreenOffIntentReceived = true;
-                    markCurrentChannelDuringScreenOff();
-                    stopAll(true);
-                    break;
-                case Intent.ACTION_SCREEN_ON:
-                    if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON");
-                    if (!mActivityResumed && mVisibleBehind) {
-                        // ACTION_SCREEN_ON is usually called after onResume. But, if media is
-                        // played under launcher with requestVisibleBehind(true), onResume will
-                        // not be called. In this case, we need to resume TvView explicitly.
-                        resumeTvIfNeeded();
+    private final BroadcastReceiver mBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    switch (intent.getAction()) {
+                        case Intent.ACTION_SCREEN_OFF:
+                            if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_OFF");
+                            // We need to stop TvView, when the screen is turned off. If not and TIS
+                            // uses MediaPlayer, a device may not go to the sleep mode and audio
+                            // can be heard, because MediaPlayer keeps playing media by its wake
+                            // lock.
+                            mScreenOffIntentReceived = true;
+                            markCurrentChannelDuringScreenOff();
+                            stopAll(true);
+                            break;
+                        case Intent.ACTION_SCREEN_ON:
+                            if (DEBUG) Log.d(TAG, "Received ACTION_SCREEN_ON");
+                            if (!mActivityResumed && mVisibleBehind) {
+                                // ACTION_SCREEN_ON is usually called after onResume. But, if media
+                                // is played under launcher with requestVisibleBehind(true),
+                                // onResume will not be called. In this case, we need to resume
+                                // TvView explicitly.
+                                resumeTvIfNeeded();
+                            }
+                            break;
+                        case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED:
+                            if (DEBUG) Log.d(TAG, "Received parental control settings change");
+                            applyParentalControlSettings();
+                            checkChannelLockNeeded(mTvView, null);
+                            break;
+                        case Intent.ACTION_TIME_CHANGED:
+                            // Re-tune the current channel to prevent incorrect behavior of
+                            // trick-play.
+                            // See: b/37393628
+                            if (mChannelTuner.getCurrentChannel() != null) {
+                                tune(true);
+                            }
+                            break;
+                        default: // fall out
                     }
-                    break;
-                case TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED:
-                    if (DEBUG) Log.d(TAG, "Received parental control settings change");
-                    applyParentalControlSettings();
-                    checkChannelLockNeeded(mTvView, null);
-                    break;
-                case Intent.ACTION_TIME_CHANGED:
-                    // Re-tune the current channel to prevent incorrect behavior of trick-play.
-                    // See: b/37393628
-                    if (mChannelTuner.getCurrentChannel() != null) {
-                        tune(true);
-                    }
-                    break;
-            }
-        }
-    };
+                }
+            };
 
     private final OnCurrentProgramUpdatedListener mOnCurrentProgramUpdatedListener =
             new OnCurrentProgramUpdatedListener() {
-        @Override
-        public void onCurrentProgramUpdated(long channelId, Program program) {
-            // Do not update channel banner by this notification
-            // when the time shifting is available.
-            if (mTimeShiftManager.isAvailable()) {
-                return;
-            }
-            Channel channel = mTvView.getCurrentChannel();
-            if (channel != null && channel.getId() == channelId) {
-                mOverlayManager.updateChannelBannerAndShowIfNeeded(
-                        TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
-                mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program);
-            }
-        }
-    };
+                @Override
+                public void onCurrentProgramUpdated(long channelId, Program program) {
+                    // Do not update channel banner by this notification
+                    // when the time shifting is available.
+                    if (mTimeShiftManager.isAvailable()) {
+                        return;
+                    }
+                    Channel channel = mTvView.getCurrentChannel();
+                    if (channel != null && channel.getId() == channelId) {
+                        mOverlayManager.updateChannelBannerAndShowIfNeeded(
+                                TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+                        mMediaSessionWrapper.update(mTvView.isBlocked(), channel, program);
+                    }
+                }
+            };
 
     private final ChannelTuner.Listener mChannelTunerListener =
             new ChannelTuner.Listener() {
                 @Override
                 public void onLoadFinished() {
-                    Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
-                            "MainActivity.mChannelTunerListener.onLoadFinished");
-                    SetupUtils.getInstance(MainActivity.this).markNewChannelsBrowsable();
+                    Debug.getTimer(Debug.TAG_START_UP_TIMER)
+                            .log("MainActivity.mChannelTunerListener.onLoadFinished");
+                    mSetupUtils.markNewChannelsBrowsable();
                     if (mActivityResumed) {
                         resumeTvIfNeeded();
                     }
@@ -389,30 +399,35 @@
                 public void onChannelChanged(Channel previousChannel, Channel currentChannel) {}
             };
 
-    private final Runnable mRestoreMainViewRunnable = new Runnable() {
-        @Override
-        public void run() {
-            restoreMainTvView();
-        }
-    };
+    private final Runnable mRestoreMainViewRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    restoreMainTvView();
+                }
+            };
     private ProgramGuideSearchFragment mSearchFragment;
 
-    private final TvInputCallback mTvInputCallback = new TvInputCallback() {
-        @Override
-        public void onInputAdded(String inputId) {
-            if (Features.TUNER.isEnabled(MainActivity.this) && mTunerInputId.equals(inputId)
-                    && TunerPreferences.shouldShowSetupActivity(MainActivity.this)) {
-                Intent intent = TunerSetupActivity.createSetupActivity(MainActivity.this);
-                startActivity(intent);
-                TunerPreferences.setShouldShowSetupActivity(MainActivity.this, false);
-                SetupUtils.getInstance(MainActivity.this).markAsKnownInput(mTunerInputId);
-            }
-        }
-    };
+    private final TvInputCallback mTvInputCallback =
+            new TvInputCallback() {
+                @Override
+                public void onInputAdded(String inputId) {
+                    if (TvFeatures.TUNER.isEnabled(MainActivity.this)
+                            && mTunerInputId.equals(inputId)
+                            && CommonPreferences.shouldShowSetupActivity(MainActivity.this)) {
+                        Intent intent =
+                                TvSingletons.getSingletons(MainActivity.this)
+                                        .getTunerSetupIntent(MainActivity.this);
+                        startActivity(intent);
+                        CommonPreferences.setShouldShowSetupActivity(MainActivity.this, false);
+                        mSetupUtils.markAsKnownInput(mTunerInputId);
+                    }
+                }
+            };
 
     private void applyParentalControlSettings() {
-        boolean parentalControlEnabled = mTvInputManagerHelper.getParentalControlSettings()
-                .isParentalControlsEnabled();
+        boolean parentalControlEnabled =
+                mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled();
         mTvView.onParentalControlChanged(parentalControlEnabled);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             ChannelPreviewUpdater.getInstance(this).updatePreviewDataForChannelsImmediately();
@@ -421,7 +436,11 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        TimerEvent timer = StubPerformanceMonitor.startBootstrapTimer();
+        mAccessibilityManager =
+                (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
+        TvSingletons tvSingletons = TvSingletons.getSingletons(this);
+        mPerformanceMonitor = tvSingletons.getPerformanceMonitor();
+        TimerEvent timer = mPerformanceMonitor.startTimer();
         DurationTimer startUpDebugTimer = Debug.getTimer(Debug.TAG_START_UP_TIMER);
         if (!startUpDebugTimer.isStarted()
                 || startUpDebugTimer.getDuration() > START_UP_TIMER_RESET_THRESHOLD_MS) {
@@ -430,30 +449,33 @@
             startUpDebugTimer.start();
         }
         startUpDebugTimer.log("MainActivity.onCreate");
-        if (DEBUG) Log.d(TAG,"onCreate()");
-        TvApplication.setCurrentRunningProcess(this, true);
+        if (DEBUG) {
+            Log.d(TAG, "onCreate()");
+        }
+        Starter.start(this);
         super.onCreate(savedInstanceState);
-        ApplicationSingletons applicationSingletons = TvApplication.getSingletons(this);
-        if (!applicationSingletons.getTvInputManagerHelper().hasTvInputManager()) {
+        if (!tvSingletons.getTvInputManagerHelper().hasTvInputManager()) {
             Log.wtf(TAG, "Stopping because device does not have a TvInputManager");
             finishAndRemoveTask();
             return;
         }
-        mPerformanceMonitor = applicationSingletons.getPerformanceMonitor();
+        mPerformanceMonitor = tvSingletons.getPerformanceMonitor();
+        mSetupUtils = tvSingletons.getSetupUtils();
 
         TvApplication tvApplication = (TvApplication) getApplication();
         mChannelDataManager = tvApplication.getChannelDataManager();
         // In API 23, TvContract.isChannelUriForPassthroughInput is hidden.
         boolean isPassthroughInput =
                 TvContract.isChannelUriForPassthroughInput(getIntent().getData());
-        boolean tuneToPassthroughInput = Intent.ACTION_VIEW.equals(getIntent().getAction())
-                && isPassthroughInput;
-        boolean channelLoadedAndNoChannelAvailable = mChannelDataManager.isDbLoadFinished()
-                && mChannelDataManager.getChannelCount() <= 0;
+        boolean tuneToPassthroughInput =
+                Intent.ACTION_VIEW.equals(getIntent().getAction()) && isPassthroughInput;
+        boolean channelLoadedAndNoChannelAvailable =
+                mChannelDataManager.isDbLoadFinished()
+                        && mChannelDataManager.getChannelCount() <= 0;
         if ((OnboardingUtils.isFirstRunWithCurrentVersion(this)
-                || channelLoadedAndNoChannelAvailable)
+                        || channelLoadedAndNoChannelAvailable)
                 && !tuneToPassthroughInput
-                && !TvCommonUtils.isRunningInTest()) {
+                && !CommonUtils.isRunningInTest()) {
             startOnboardingActivity();
             return;
         }
@@ -462,31 +484,38 @@
         mTvInputManagerHelper = tvApplication.getTvInputManagerHelper();
         mTvView = (TunableTvView) findViewById(R.id.main_tunable_tv_view);
         mTvView.initialize(mProgramDataManager, mTvInputManagerHelper);
-        mTvView.setOnUnhandledInputEventListener(new OnUnhandledInputEventListener() {
-            @Override
-            public boolean onUnhandledInputEvent(InputEvent event) {
-                if (isKeyEventBlocked()) {
-                    return true;
-                }
-                if (event instanceof KeyEvent) {
-                    KeyEvent keyEvent = (KeyEvent) event;
-                    if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.isLongPress()) {
-                        if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
+        mTvView.setOnUnhandledInputEventListener(
+                new OnUnhandledInputEventListener() {
+                    @Override
+                    public boolean onUnhandledInputEvent(InputEvent event) {
+                        if (DEBUG) {
+                            Log.d(TAG, "onUnhandledInputEvent " + event);
+                        }
+                        if (isKeyEventBlocked()) {
                             return true;
                         }
+                        if (event instanceof KeyEvent) {
+                            KeyEvent keyEvent = (KeyEvent) event;
+                            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
+                                    && keyEvent.isLongPress()) {
+                                if (onKeyLongPress(keyEvent.getKeyCode(), keyEvent)) {
+                                    return true;
+                                }
+                            }
+                            if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
+                                return onKeyUp(keyEvent.getKeyCode(), keyEvent);
+                            } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                                return onKeyDown(keyEvent.getKeyCode(), keyEvent);
+                            }
+                        }
+                        return false;
                     }
-                    if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
-                        return onKeyUp(keyEvent.getKeyCode(), keyEvent);
-                    } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
-                        return onKeyDown(keyEvent.getKeyCode(), keyEvent);
-                    }
-                }
-                return false;
-            }
-        });
+                });
+        mTvView.setOnTalkBackDpadKeyListener(keycode -> handleUpDownKeys(keycode, null));
         long channelId = Utils.getLastWatchedChannelId(this);
         String inputId = Utils.getLastWatchedTunerInputId(this);
-        if (!isPassthroughInput && inputId != null
+        if (!isPassthroughInput
+                && inputId != null
                 && channelId != Channel.INVALID_ID) {
             mTvView.warmUpInput(inputId, TvContract.buildChannelUri(channelId));
         }
@@ -496,12 +525,12 @@
             Toast.makeText(this, "Using Strict Mode for eng builds", Toast.LENGTH_SHORT).show();
         }
         mTracker = tvApplication.getTracker();
-        if (Features.TUNER.isEnabled(this)) {
+        if (TvFeatures.TUNER.isEnabled(this)) {
             mTvInputManagerHelper.addCallback(mTvInputCallback);
         }
-        mTunerInputId = TunerTvInputService.getInputId(this);
-        mProgramDataManager.addOnCurrentProgramUpdatedListener(Channel.INVALID_ID,
-                mOnCurrentProgramUpdatedListener);
+        mTunerInputId = tvSingletons.getEmbeddedTunerInputId();
+        mProgramDataManager.addOnCurrentProgramUpdatedListener(
+                Channel.INVALID_ID, mOnCurrentProgramUpdatedListener);
         mProgramDataManager.setPrefetchEnabled(true);
         mChannelTuner = new ChannelTuner(mChannelDataManager, mTvInputManagerHelper);
         mChannelTuner.addListener(mChannelTunerListener);
@@ -512,91 +541,126 @@
         if (CommonFeatures.DVR.isEnabled(this)) {
             mDvrManager = tvApplication.getDvrManager();
         }
-        mTimeShiftManager = new TimeShiftManager(this, mTvView, mProgramDataManager, mTracker,
-                new OnCurrentProgramUpdatedListener() {
-                    @Override
-                    public void onCurrentProgramUpdated(long channelId, Program program) {
-                        mMediaSessionWrapper.update(mTvView.isBlocked(), getCurrentChannel(),
-                                program);
-                        switch (mTimeShiftManager.getLastActionId()) {
-                            case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND:
-                            case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD:
-                            case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS:
-                            case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT:
-                                mOverlayManager.updateChannelBannerAndShowIfNeeded(
-                                        TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
-                                break;
-                            case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE:
-                            case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY:
-                            default:
-                                mOverlayManager.updateChannelBannerAndShowIfNeeded(
-                                        TvOverlayManager.UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
-                                break;
-                        }
-                    }
-                });
+        mTimeShiftManager =
+                new TimeShiftManager(
+                        this,
+                        mTvView,
+                        mProgramDataManager,
+                        mTracker,
+                        new OnCurrentProgramUpdatedListener() {
+                            @Override
+                            public void onCurrentProgramUpdated(long channelId, Program program) {
+                                mMediaSessionWrapper.update(
+                                        mTvView.isBlocked(), getCurrentChannel(), program);
+                                switch (mTimeShiftManager.getLastActionId()) {
+                                    case TimeShiftManager.TIME_SHIFT_ACTION_ID_REWIND:
+                                    case TimeShiftManager.TIME_SHIFT_ACTION_ID_FAST_FORWARD:
+                                    case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_PREVIOUS:
+                                    case TimeShiftManager.TIME_SHIFT_ACTION_ID_JUMP_TO_NEXT:
+                                        mOverlayManager.updateChannelBannerAndShowIfNeeded(
+                                                TvOverlayManager
+                                                        .UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
+                                        break;
+                                    case TimeShiftManager.TIME_SHIFT_ACTION_ID_PAUSE:
+                                    case TimeShiftManager.TIME_SHIFT_ACTION_ID_PLAY:
+                                    default:
+                                        mOverlayManager.updateChannelBannerAndShowIfNeeded(
+                                                TvOverlayManager
+                                                        .UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO);
+                                        break;
+                                }
+                            }
+                        });
 
         DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
         Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
         mDefaultRefreshRate = display.getRefreshRate();
 
         if (!PermissionUtils.hasAccessWatchedHistory(this)) {
-            WatchedHistoryManager watchedHistoryManager = new WatchedHistoryManager(
-                    getApplicationContext());
+            WatchedHistoryManager watchedHistoryManager =
+                    new WatchedHistoryManager(getApplicationContext());
             watchedHistoryManager.start();
             mTvView.setWatchedHistoryManager(watchedHistoryManager);
         }
-        mTvViewUiManager = new TvViewUiManager(this, mTvView,
-                (FrameLayout) findViewById(android.R.id.content), mTvOptionsManager);
+        mTvViewUiManager =
+                new TvViewUiManager(
+                        this,
+                        mTvView,
+                        (FrameLayout) findViewById(android.R.id.content),
+                        mTvOptionsManager);
 
         mContentView = findViewById(android.R.id.content);
         ViewGroup sceneContainer = (ViewGroup) findViewById(R.id.scene_container);
-        ChannelBannerView channelBannerView = (ChannelBannerView) getLayoutInflater().inflate(
-                R.layout.channel_banner, sceneContainer, false);
-        KeypadChannelSwitchView keypadChannelSwitchView = (KeypadChannelSwitchView)
-                getLayoutInflater().inflate(R.layout.keypad_channel_switch, sceneContainer, false);
-        InputBannerView inputBannerView = (InputBannerView) getLayoutInflater()
-                .inflate(R.layout.input_banner, sceneContainer, false);
-        SelectInputView selectInputView = (SelectInputView) getLayoutInflater()
-                .inflate(R.layout.select_input, sceneContainer, false);
-        selectInputView.setOnInputSelectedCallback(new OnInputSelectedCallback() {
-            @Override
-            public void onTunerInputSelected() {
-                Channel currentChannel = mChannelTuner.getCurrentChannel();
-                if (currentChannel != null && !currentChannel.isPassthrough()) {
-                    hideOverlays();
-                } else {
-                    tuneToLastWatchedChannelForTunerInput();
-                }
-            }
+        ChannelBannerView channelBannerView =
+                (ChannelBannerView)
+                        getLayoutInflater().inflate(R.layout.channel_banner, sceneContainer, false);
+        KeypadChannelSwitchView keypadChannelSwitchView =
+                (KeypadChannelSwitchView)
+                        getLayoutInflater()
+                                .inflate(R.layout.keypad_channel_switch, sceneContainer, false);
+        InputBannerView inputBannerView =
+                (InputBannerView)
+                        getLayoutInflater().inflate(R.layout.input_banner, sceneContainer, false);
+        SelectInputView selectInputView =
+                (SelectInputView)
+                        getLayoutInflater().inflate(R.layout.select_input, sceneContainer, false);
+        selectInputView.setOnInputSelectedCallback(
+                new OnInputSelectedCallback() {
+                    @Override
+                    public void onTunerInputSelected() {
+                        Channel currentChannel = mChannelTuner.getCurrentChannel();
+                        if (currentChannel != null && !currentChannel.isPassthrough()) {
+                            hideOverlays();
+                        } else {
+                            tuneToLastWatchedChannelForTunerInput();
+                        }
+                    }
 
-            @Override
-            public void onPassthroughInputSelected(@NonNull TvInputInfo input) {
-                Channel currentChannel = mChannelTuner.getCurrentChannel();
-                String currentInputId = currentChannel == null ? null : currentChannel.getInputId();
-                if (TextUtils.equals(input.getId(), currentInputId)) {
-                    hideOverlays();
-                } else {
-                    tuneToChannel(Channel.createPassthroughChannel(input.getId()));
-                }
-            }
+                    @Override
+                    public void onPassthroughInputSelected(@NonNull TvInputInfo input) {
+                        Channel currentChannel = mChannelTuner.getCurrentChannel();
+                        String currentInputId =
+                                currentChannel == null ? null : currentChannel.getInputId();
+                        if (TextUtils.equals(input.getId(), currentInputId)) {
+                            hideOverlays();
+                        } else {
+                            tuneToChannel(ChannelImpl.createPassthroughChannel(input.getId()));
+                        }
+                    }
 
-            private void hideOverlays() {
-                getOverlayManager().hideOverlays(
-                        TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
-                        | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
-                        | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
-                        | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
-                        | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
-            }
-        });
+                    private void hideOverlays() {
+                        getOverlayManager()
+                                .hideOverlays(
+                                        TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+                                                | TvOverlayManager
+                                                        .FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+                                                | TvOverlayManager
+                                                        .FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE
+                                                | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU
+                                                | TvOverlayManager
+                                                        .FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+                    }
+                });
         mSearchFragment = new ProgramGuideSearchFragment();
-        mOverlayManager = new TvOverlayManager(this, mChannelTuner, mTvView, mTvOptionsManager,
-                keypadChannelSwitchView, channelBannerView, inputBannerView,
-                selectInputView, sceneContainer, mSearchFragment);
+        mOverlayManager =
+                new TvOverlayManager(
+                        this,
+                        mChannelTuner,
+                        mTvView,
+                        mTvOptionsManager,
+                        keypadChannelSwitchView,
+                        channelBannerView,
+                        inputBannerView,
+                        selectInputView,
+                        sceneContainer,
+                        mSearchFragment);
+        mAccessibilityManager.addAccessibilityStateChangeListener(mOverlayManager);
 
         mAudioManagerHelper = new AudioManagerHelper(this, mTvView);
-        mMediaSessionWrapper = new MediaSessionWrapper(this);
+        Intent nowPlayingIntent = new Intent(this, MainActivity.class);
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(this, REQUEST_CODE_NOW_PLAYING, nowPlayingIntent, 0);
+        mMediaSessionWrapper = new MediaSessionWrapper(this, pendingIntent);
 
         mTvViewUiManager.restoreDisplayMode(false);
         if (!handleIntent(getIntent())) {
@@ -604,18 +668,21 @@
             return;
         }
 
-        mAccessibilityManager =
-                (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
-        mSendConfigInfoRecurringRunner = new RecurringRunner(this, TimeUnit.DAYS.toMillis(1),
-                new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper), null);
+        mSendConfigInfoRecurringRunner =
+                new RecurringRunner(
+                        this,
+                        TimeUnit.DAYS.toMillis(1),
+                        new SendConfigInfoRunnable(mTracker, mTvInputManagerHelper),
+                        null);
         mSendConfigInfoRecurringRunner.start();
-        mChannelStatusRecurringRunner = SendChannelStatusRunnable
-                .startChannelStatusRecurringRunner(this, mTracker, mChannelDataManager);
+        mChannelStatusRecurringRunner =
+                SendChannelStatusRunnable.startChannelStatusRecurringRunner(
+                        this, mTracker, mChannelDataManager);
 
         // To avoid not updating Rating systems when changing language.
         mTvInputManagerHelper.getContentRatingsManager().update();
         if (CommonFeatures.DVR.isEnabled(this)
-                && Features.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) {
+                && TvFeatures.SHOW_UPCOMING_CONFLICT_DIALOG.isEnabled(this)) {
             mDvrConflictChecker = new ConflictChecker(this);
         }
         initForTest();
@@ -632,13 +699,14 @@
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         float density = getResources().getDisplayMetrics().density;
-        mTvViewUiManager.onConfigurationChanged((int) (newConfig.screenWidthDp * density),
+        mTvViewUiManager.onConfigurationChanged(
+                (int) (newConfig.screenWidthDp * density),
                 (int) (newConfig.screenHeightDp * density));
     }
 
     @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
-            @NonNull int[] grantResults) {
+    public void onRequestPermissionsResult(
+            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
         if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) {
             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                 // Start reload of dependent data
@@ -650,14 +718,18 @@
                 finish();
                 startActivity(intent);
             } else {
-                Toast.makeText(this, R.string.msg_read_tv_listing_permission_denied,
-                        Toast.LENGTH_LONG).show();
+                Toast.makeText(
+                                this,
+                                R.string.msg_read_tv_listing_permission_denied,
+                                Toast.LENGTH_LONG)
+                        .show();
                 finish();
             }
         }
     }
 
-    @BlockScreenType private int getDesiredBlockScreenType() {
+    @BlockScreenType
+    private int getDesiredBlockScreenType() {
         if (!mActivityResumed) {
             return TunableTvView.BLOCK_SCREEN_TYPE_NO_UI;
         }
@@ -689,7 +761,9 @@
 
     @Override
     protected void onNewIntent(Intent intent) {
-        if (DEBUG) Log.d(TAG,"onNewIntent(): " + intent);
+        if (DEBUG) {
+            Log.d(TAG, "onNewIntent(): " + intent);
+        }
         if (mOverlayManager == null) {
             // It's called before onCreate. The intent will be handled at onCreate. b/30725058
             return;
@@ -705,7 +779,9 @@
     @Override
     protected void onStart() {
         TimerEvent timer = mPerformanceMonitor.startTimer();
-        if (DEBUG) Log.d(TAG,"onStart()");
+        if (DEBUG) {
+            Log.d(TAG, "onStart()");
+        }
         super.onStart();
         mScreenOffIntentReceived = false;
         mActivityStarted = true;
@@ -720,9 +796,9 @@
             notificationIntent.setAction(NotificationService.ACTION_SHOW_RECOMMENDATION);
             startService(notificationIntent);
         }
-        TunerInputController.executeNetworkTunerDiscoveryAsyncTask(this);
-
-        EpgFetcher.getInstance(this).fetchImmediatelyIfNeeded();
+        TvSingletons singletons = TvSingletons.getSingletons(this);
+        singletons.getTunerInputController().executeNetworkTunerDiscoveryAsyncTask(this);
+        singletons.getEpgFetcher().fetchImmediatelyIfNeeded();
         mPerformanceMonitor.stopTimer(timer, EventNames.MAIN_ACTIVITY_ONSTART);
     }
 
@@ -732,10 +808,12 @@
         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("MainActivity.onResume start");
         if (DEBUG) Log.d(TAG, "onResume()");
         super.onResume();
+        mIsInPIPMode = false;
         if (!PermissionUtils.hasAccessAllEpg(this)
                 && checkSelfPermission(PERMISSION_READ_TV_LISTINGS)
-                    != PackageManager.PERMISSION_GRANTED) {
-            requestPermissions(new String[]{PERMISSION_READ_TV_LISTINGS},
+                        != PackageManager.PERMISSION_GRANTED) {
+            requestPermissions(
+                    new String[] {PERMISSION_READ_TV_LISTINGS},
                     PERMISSIONS_REQUEST_READ_TV_LISTINGS);
         }
         mTracker.sendScreenView(SCREEN_NAME);
@@ -755,19 +833,20 @@
         Set<String> failedScheduledRecordingInfoSet =
                 Utils.getFailedScheduledRecordingInfoSet(getApplicationContext());
         if (Utils.hasRecordingFailedReason(
-                getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)
+                        getApplicationContext(), TvInputManager.RECORDING_ERROR_INSUFFICIENT_SPACE)
                 && !failedScheduledRecordingInfoSet.isEmpty()) {
-            runAfterAttachedToWindow(new Runnable() {
-                @Override
-                public void run() {
-                    DvrUiHelper.showDvrInsufficientSpaceErrorDialog(MainActivity.this,
-                            failedScheduledRecordingInfoSet);
-                }
-            });
+            runAfterAttachedToWindow(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            DvrUiHelper.showDvrInsufficientSpaceErrorDialog(
+                                    MainActivity.this, failedScheduledRecordingInfoSet);
+                        }
+                    });
         }
 
         if (mChannelTuner.areAllChannelsLoaded()) {
-            SetupUtils.getInstance(this).markNewChannelsBrowsable();
+            mSetupUtils.markNewChannelsBrowsable();
             resumeTvIfNeeded();
         }
         mOverlayManager.showMenuWithTimeShiftPauseIfNeeded();
@@ -779,28 +858,29 @@
             mInputToSetUp = null;
         } else if (mShowProgramGuide) {
             mShowProgramGuide = false;
-            mHandler.post(new Runnable() {
-                // This will delay the start of the animation until after the Live Channel app is
-                // shown. Without this the animation is completed before it is actually visible on
-                // the screen.
-                @Override
-                public void run() {
-                    mOverlayManager.showProgramGuide();
-                }
-            });
+            // This will delay the start of the animation until after the Live Channel app is
+            // shown. Without this the animation is completed before it is actually visible on
+            // the screen.
+            mHandler.post(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            mOverlayManager.showProgramGuide();
+                        }
+                    });
         } else if (mShowSelectInputView) {
             mShowSelectInputView = false;
-            mHandler.post(new Runnable() {
-                // mShowSelectInputView is true when the activity is started/resumed because the
-                // TV_INPUT button was pressed in a different app.
-                // This will delay the start of the animation until after the Live Channel app is
-                // shown. Without this the animation is completed before it is actually visible on
-                // the screen.
-                @Override
-                public void run() {
-                    mOverlayManager.showSelectInputView();
-                }
-            });
+            // mShowSelectInputView is true when the activity is started/resumed because the
+            // TV_INPUT button was pressed in a different app.  This will delay the start of
+            // the animation until after the Live Channel app is shown. Without this the
+            // animation is completed before it is actually visible on the screen.
+            mHandler.post(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            mOverlayManager.showSelectInputView();
+                        }
+                    });
         }
         if (mDvrConflictChecker != null) {
             mDvrConflictChecker.start();
@@ -823,25 +903,26 @@
         mShowLockedChannelsTemporarily = false;
         mShouldTuneToTunerChannel = false;
         if (!mVisibleBehind) {
-            mAudioManagerHelper.abandonAudioFocus();
-            mMediaSessionWrapper.setPlaybackState(false);
-            mTracker.sendScreenView("");
+            if (mIsInPIPMode) {
+                mTracker.sendScreenView(SCREEN_PIP);
+            } else {
+                mTracker.sendScreenView("");
+                mAudioManagerHelper.abandonAudioFocus();
+                mMediaSessionWrapper.setPlaybackState(false);
+            }
         } else {
             mTracker.sendScreenView(SCREEN_BEHIND_NAME);
         }
+        TvSingletons.getSingletons(this).getExperimentLoader().asyncRefreshExperiments(this);
         super.onPause();
     }
 
-    /**
-     * Returns true if {@link #onResume} is called and {@link #onPause} is not called yet.
-     */
+    /** Returns true if {@link #onResume} is called and {@link #onPause} is not called yet. */
     public boolean isActivityResumed() {
         return mActivityResumed;
     }
 
-    /**
-     * Returns true if {@link #onStart} is called and {@link #onStop} is not called yet.
-     */
+    /** Returns true if {@link #onStart} is called and {@link #onStop} is not called yet. */
     public boolean isActivityStarted() {
         return mActivityStarted;
     }
@@ -867,12 +948,14 @@
                     mTvView.unblockContent(unblockedRating);
                     break;
                 case PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN:
-                    mOverlayManager.getSideFragmentManager()
+                    mOverlayManager
+                            .getSideFragmentManager()
                             .show(new ParentalControlsFragment(), false);
-                    // Pass through.
+                    // fall through.
                 case PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN:
                     mOverlayManager.getSideFragmentManager().showSidePanel(true);
                     break;
+                default: // fall out
             }
         } else if (type == PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN) {
             mOverlayManager.getSideFragmentManager().hideAll(false);
@@ -881,7 +964,8 @@
 
     private void resumeTvIfNeeded() {
         if (DEBUG) Log.d(TAG, "resumeTvIfNeeded()");
-        if (!mTvView.isPlaying() || mInitChannelUri != null
+        if (!mTvView.isPlaying()
+                || mInitChannelUri != null
                 || (mShouldTuneToTunerChannel && mChannelTuner.isCurrentChannelPassthrough())) {
             if (TvContract.isChannelUriForPassthroughInput(mInitChannelUri)) {
                 // The target input may not be ready yet, especially, just after screen on.
@@ -917,9 +1001,11 @@
             // is playing, we stop the passthrough TV input.
             stopTv();
         }
-        SoftPreconditions.checkState(TvContract.isChannelUriForPassthroughInput(channelUri)
-                || mChannelTuner.areAllChannelsLoaded(),
-                TAG, "startTV assumes that ChannelDataManager is already loaded.");
+        SoftPreconditions.checkState(
+                TvContract.isChannelUriForPassthroughInput(channelUri)
+                        || mChannelTuner.areAllChannelsLoaded(),
+                TAG,
+                "startTV assumes that ChannelDataManager is already loaded.");
         if (mTvView.isPlaying()) {
             // TV has already started.
             if (channelUri == null || channelUri.equals(mChannelTuner.getCurrentChannelUri())) {
@@ -945,15 +1031,19 @@
             mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
         } else {
             if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
-                Channel channel = Channel.createPassthroughChannel(channelUri);
+                ChannelImpl channel = ChannelImpl.createPassthroughChannel(channelUri);
                 mChannelTuner.moveToChannel(channel);
             } else {
                 long channelId = ContentUris.parseId(channelUri);
                 Channel channel = mChannelDataManager.getChannel(channelId);
                 if (channel == null || !mChannelTuner.moveToChannel(channel)) {
                     mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0));
-                    Log.w(TAG, "The requested channel (id=" + channelId + ") doesn't exist. "
-                            + "The first channel will be tuned to.");
+                    Log.w(
+                            TAG,
+                            "The requested channel (id="
+                                    + channelId
+                                    + ") doesn't exist. "
+                                    + "The first channel will be tuned to.");
                 }
             }
         }
@@ -985,9 +1075,7 @@
         super.onStop();
     }
 
-    /**
-     * Handles screen off to keep the current channel for next screen on.
-     */
+    /** Handles screen off to keep the current channel for next screen on. */
     private void markCurrentChannelDuringScreenOff() {
         mInitChannelUri = mChannelTuner.getCurrentChannelUri();
         if (mChannelTuner.isCurrentChannelPassthrough()) {
@@ -1015,7 +1103,7 @@
      * @param calledByPopup If true, startSetupActivity is invoked from the setup fragment.
      */
     public void startSetupActivity(TvInputInfo input, boolean calledByPopup) {
-        Intent intent = TvCommonUtils.createSetupIntent(input);
+        Intent intent = CommonUtils.createSetupIntent(input);
         if (intent == null) {
             Toast.makeText(this, R.string.msg_no_setup_activity, Toast.LENGTH_SHORT).show();
             return;
@@ -1038,16 +1126,23 @@
             startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
         } catch (ActivityNotFoundException e) {
             mInputIdUnderSetup = null;
-            Toast.makeText(this, getString(R.string.msg_unable_to_start_setup_activity,
-                    input.loadLabel(this)), Toast.LENGTH_SHORT).show();
+            Toast.makeText(
+                            this,
+                            getString(
+                                    R.string.msg_unable_to_start_setup_activity,
+                                    input.loadLabel(this)),
+                            Toast.LENGTH_SHORT)
+                    .show();
             return;
         }
         if (calledByPopup) {
-            mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
-                    | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+            mOverlayManager.hideOverlays(
+                    TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
+                            | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
         } else {
-            mOverlayManager.hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
-                    | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
+            mOverlayManager.hideOverlays(
+                    TvOverlayManager.FLAG_HIDE_OVERLAYS_WITHOUT_ANIMATION
+                            | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY);
         }
     }
 
@@ -1060,8 +1155,11 @@
         try {
             startActivitySafe(intent);
         } catch (ActivityNotFoundException e) {
-            Toast.makeText(this, getString(R.string.msg_unable_to_start_system_captioning_settings),
-                    Toast.LENGTH_SHORT).show();
+            Toast.makeText(
+                            this,
+                            getString(R.string.msg_unable_to_start_system_captioning_settings),
+                            Toast.LENGTH_SHORT)
+                    .show();
         }
     }
 
@@ -1085,16 +1183,12 @@
         return mTimeShiftManager;
     }
 
-    /**
-     * Returns the instance of {@link TvOverlayManager}.
-     */
+    /** Returns the instance of {@link TvOverlayManager}. */
     public TvOverlayManager getOverlayManager() {
         return mOverlayManager;
     }
 
-    /**
-     * Returns the {@link ConflictChecker}.
-     */
+    /** Returns the {@link ConflictChecker}. */
     @Nullable
     public ConflictChecker getDvrConflictChecker() {
         return mDvrConflictChecker;
@@ -1109,9 +1203,10 @@
     }
 
     /**
-     * Returns the current program which the user is watching right now.<p>
+     * Returns the current program which the user is watching right now.
      *
-     * It might be a live program. If the time shifting is available, it can be a past program, too.
+     * <p>It might be a live program. If the time shifting is available, it can be a past program,
+     * too.
      */
     public Program getCurrentProgram() {
         if (!isChannelChangeKeyDownReceived() && mTimeShiftManager.isAvailable()) {
@@ -1122,9 +1217,9 @@
     }
 
     /**
-     * Returns the current playing time in milliseconds.<p>
+     * Returns the current playing time in milliseconds.
      *
-     * If the time shifting is available, the time is the playing position of the program,
+     * <p>If the time shifting is available, the time is the playing position of the program,
      * otherwise, the system current time.
      */
     public long getCurrentPlayingPosition() {
@@ -1152,18 +1247,7 @@
         LauncherActivity.startActivitySafe(this, intent);
     }
 
-    /**
-     * Call {@link Activity#startActivityForResult} in a safe way.
-     *
-     * @see LauncherActivity
-     */
-    private void startActivityForResultSafe(Intent intent, int requestCode) {
-        LauncherActivity.startActivityForResultSafe(this, intent, requestCode);
-    }
-
-    /**
-     * Show settings fragment.
-     */
+    /** Show settings fragment. */
     public void showSettingsFragment() {
         if (!mChannelTuner.areAllChannelsLoaded()) {
             // Show ChannelSourcesFragment only if all the channels are loaded.
@@ -1180,8 +1264,8 @@
      * It is called when shrunken TvView is desired, such as EditChannelFragment and
      * ChannelsLockedFragment.
      */
-    public void startShrunkenTvView(boolean showLockedChannelsTemporarily,
-            boolean willMainViewBeTunerInput) {
+    public void startShrunkenTvView(
+            boolean showLockedChannelsTemporarily, boolean willMainViewBeTunerInput) {
         mChannelBeforeShrunkenTvView = mTvView.getCurrentChannel();
         mWasChannelUnblockedBeforeShrunkenByUser = mIsCurrentChannelUnblockedByUser;
         mAllowedRatingBeforeShrunken = mLastAllowedRatingForCurrentChannel;
@@ -1214,19 +1298,21 @@
         // The current channel is mTvView.getCurrentChannel() and need to tune to the returnChannel.
         if (!Objects.equals(mTvView.getCurrentChannel(), returnChannel)) {
             final Channel channel = returnChannel;
-            Runnable tuneAction = new Runnable() {
-                @Override
-                public void run() {
-                    tuneToChannel(channel);
-                    if (mChannelBeforeShrunkenTvView == null
-                            || !mChannelBeforeShrunkenTvView.equals(channel)) {
-                        Utils.setLastWatchedChannel(MainActivity.this, channel);
-                    }
-                    mIsCompletingShrunkenTvView = false;
-                    mIsCurrentChannelUnblockedByUser = mWasChannelUnblockedBeforeShrunkenByUser;
-                    mTvView.setBlockScreenType(getDesiredBlockScreenType());
-                }
-            };
+            Runnable tuneAction =
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            tuneToChannel(channel);
+                            if (mChannelBeforeShrunkenTvView == null
+                                    || !mChannelBeforeShrunkenTvView.equals(channel)) {
+                                Utils.setLastWatchedChannel(MainActivity.this, channel);
+                            }
+                            mIsCompletingShrunkenTvView = false;
+                            mIsCurrentChannelUnblockedByUser =
+                                    mWasChannelUnblockedBeforeShrunkenByUser;
+                            mTvView.setBlockScreenType(getDesiredBlockScreenType());
+                        }
+                    };
             mTvViewUiManager.fadeOutTvView(tuneAction);
             // Will automatically fade-in when video becomes available.
         } else {
@@ -1247,35 +1333,45 @@
      */
     public boolean isScreenBlockedByResourceConflictOrParentalControl() {
         return mTvView.getVideoUnavailableReason()
-                == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE || mTvView.isBlocked();
+                        == TunableTvView.VIDEO_UNAVAILABLE_REASON_NO_RESOURCE
+                || mTvView.isBlocked();
     }
 
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == REQUEST_CODE_START_SETUP_ACTIVITY) {
-            if (resultCode == RESULT_OK) {
-                int count = mChannelDataManager.getChannelCountForInput(mInputIdUnderSetup);
-                String text;
-                if (count > 0) {
-                    text = getResources().getQuantityString(R.plurals.msg_channel_added,
-                            co