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