diff options
author | Live Channels Team <no-reply@google.com> | 2018-02-16 10:34:47 -0800 |
---|---|---|
committer | Nick Chalko <nchalko@google.com> | 2018-02-16 11:50:03 -0800 |
commit | 0cc0713c1bf8027642987b750b80217569d2932a (patch) | |
tree | afafc2e82a5626b383e495635c843c51ceb985c5 | |
parent | ee959c2539b768dd3fbcbdf70acb03e536a9d76d (diff) | |
download | TV-0cc0713c1bf8027642987b750b80217569d2932a.tar.gz |
Changes imported from Live Channels
- 186014614 Sync more make files with master by nchalko <nchalko@google.com>
- 186013553 FIX: safely parse URI from intents. by nchalko <nchalko@google.com>
- 186010488 Add missing license header by nchalko <nchalko@google.com>
- 186009400 Sync all make files with master by nchalko <nchalko@google.com>
- 185891159 Extract a Channel interface by nchalko <nchalko@google.com>
- 185885678 Make recording history available by shubang <shubang@google.com>
- 185725900 Extract an interface for TunableTvView by nchalko <nchalko@google.com>
- 185724604 CLEANUP: Move ImageLoader to a separate package by nchalko <nchalko@google.com>
- 185722979 CLEANUP: Fix amiguous method reference by renaming static... by nchalko <nchalko@google.com>
- 185721563 CLEANUP: create a seperate top level target for resources by nchalko <nchalko@google.com>
- 185720102 CLEANUP: fix comparison using reference equality instead ... by nchalko <nchalko@google.com>
- 185717674 CLEANUP: Move MemoryManageable to a separate package by nchalko <nchalko@google.com>
- 185609615 FIX: Improve Program Guide description for talkback by nchalko <nchalko@google.com>
- 185607602 PARTIAL: Speak the channel number for each item in the pr... by nchalko <nchalko@google.com>
- 185552957 CLEANUP: Move TestUtils to tests/common by nchalko <nchalko@google.com>
- 185549529 PARTIAL: Add program description to the content discripti... by nchalko <nchalko@google.com>
- 185521822 Use robolectric 3.6.1 in android.mk by nchalko <nchalko@google.com>
- 185521733 Add recording info to ProgramItemView content description by nchalko <nchalko@google.com>
- 185459218 Add time to ProgramItemView content description by nchalko <nchalko@google.com>
- 185449505 Add clock to the Utils.getDurationString by nchalko <nchalko@google.com>
- 185435931 Inject the clock in ProgramItemView for testing by nchalko <nchalko@google.com>
- 185435648 Fix link-type warnings by nchalko <nchalko@google.com>
- 185435488 Migrate to AAPT2 by nchalko <nchalko@google.com>
- 185434148 Add uptimeMillis to the Clock interface by nchalko <nchalko@google.com>
PiperOrigin-RevId: 186014614
Change-Id: I583af9ac3e56161736504024b62d1fd62e31c15a
Test: tested in google3
128 files changed, 1705 insertions, 589 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 970c3046..3456f16b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -13,15 +13,18 @@ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. - --> - +--> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.tv" xmlns:tools="http://schemas.android.com/tools"> + package="com.android.tv" > + + <uses-sdk + android:minSdkVersion="23" + android:targetSdkVersion="26" /> - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE" /> - <uses-permission android:name="android.permission.GLOBAL_SEARCH" tools:ignore="ProtectedPermissions"/> + <uses-permission android:name="android.permission.GLOBAL_SEARCH" /> <uses-permission android:name="android.permission.HDMI_CEC" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.MODIFY_PARENTAL_CONTROLS" /> @@ -33,56 +36,94 @@ <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <uses-permission android:name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" /> <uses-permission android:name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" /> - <!-- Permissions/feature for USB tuner --> <uses-permission android:name="android.permission.DVB_DEVICE" /> - <uses-feature android:name="android.hardware.usb.host" android:required="false" /> + + <uses-feature + android:name="android.hardware.usb.host" + android:required="false" /> <!-- Limit only for Android TV --> - <uses-feature android:name="android.software.leanback" android:required="true" /> - <uses-feature android:name="android.software.live_tv" android:required="true" /> - <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> + <uses-feature + android:name="android.software.leanback" + android:required="true" /> + <uses-feature + android:name="android.software.live_tv" + android:required="true" /> + <uses-feature + android:name="android.hardware.touchscreen" + android:required="false" /> <!-- Receives input events from the TV app. --> - <permission android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT" - android:protectionLevel="signatureOrSystem" - android:label="@string/permlab_receiveInputEvent" + <permission + android:name="com.android.tv.permission.RECEIVE_INPUT_EVENT" android:description="@string/permdesc_receiveInputEvent" - tools:ignore="SignatureOrSystemPermissions"/> + android:label="@string/permlab_receiveInputEvent" + android:protectionLevel="signatureOrSystem" /> <!-- Customizes Live TV with customization packages. --> - <permission android:name="com.android.tv.permission.CUSTOMIZE_TV_APP" - android:protectionLevel="signatureOrSystem" - android:label="@string/permlab_customizeTvApp" + <permission + android:name="com.android.tv.permission.CUSTOMIZE_TV_APP" android:description="@string/permdesc_customizeTvApp" - tools:ignore="SignatureOrSystemPermissions"/> - - <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/> + android:label="@string/permlab_customizeTvApp" + android:protectionLevel="signatureOrSystem" /> - <application android:label="@string/app_name" + <application android:name="com.android.tv.app.LiveTvApplication" android:allowBackup="true" - android:icon="@drawable/ic_live_channels" android:banner="@drawable/banner" + android:icon="@drawable/ic_live_channels" + android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/Theme.TV"> - <activity android:name="com.android.tv.TvActivity"> + android:theme="@style/Theme.TV" > + <activity + android:name="com.android.tv.tuner.setup.LiveTvTunerSetupActivity" + android:configChanges="keyboard|keyboardHidden" + android:label="@string/bt_app_name" + android:launchMode="singleInstance" + android:process="com.android.tv.tuner" + android:theme="@style/Theme.Setup.GuidedStep" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + </activity> + + <!-- providers are listed here to keep them separate from the internal versions --> + <provider + android:name="com.android.tv.search.LocalSearchProvider" + android:authorities="com.android.tv.search" + android:enabled="true" + android:exported="true" > + <meta-data + android:name="SupportedSwitchActionType" + android:value="CHANNEL|TVINPUT" /> + </provider> + <provider + android:name="com.android.tv.common.CommonPreferenceProvider" + android:authorities="com.android.tv.common.preferences" + android:exported="false" + android:process="com.android.tv.common" /> + + <activity android:name="com.android.tv.TvActivity" > <intent-filter> <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> </intent-filter> </activity> - - <activity android:name="com.android.tv.MainActivity" + <activity + android:name="com.android.tv.MainActivity" android:configChanges="keyboard|keyboardHidden|screenSize|smallestScreenSize|screenLayout|orientation" - android:screenOrientation="landscape" android:launchMode="singleTask" android:resizeableActivity="true" + android:screenOrientation="landscape" android:supportsPictureInPicture="true" - android:theme="@style/Theme.TV.MainActivity"> + android:theme="@style/Theme.TV.MainActivity" > <intent-filter> <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="vnd.android.cursor.item/channel" /> <data android:mimeType="vnd.android.cursor.dir/channel" /> <data android:mimeType="vnd.android.cursor.item/program" /> @@ -90,170 +131,146 @@ </intent-filter> <intent-filter> <action android:name="android.media.tv.action.SETUP_INPUTS" /> + <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEARCH" /> </intent-filter> - <meta-data android:name="supports_leanback" android:value="true" /> - <meta-data android:name="android.app.searchable" + + <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" + <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" + <activity + android:name="com.android.tv.SetupPassthroughActivity" android:configChanges="keyboard|keyboardHidden" - android:theme="@android:style/Theme.Translucent.NoTitleBar"> + android:exported="true" + android:theme="@android:style/Theme.Translucent.NoTitleBar" > <intent-filter> <action android:name="com.android.tv.action.LAUNCH_INPUT_SETUP" /> + <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> - - <activity android:name="com.android.tv.SelectInputActivity" + <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" + <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" + <activity + android:name="com.android.tv.dvr.ui.browse.DvrBrowseActivity" android:configChanges="keyboard|keyboardHidden" android:launchMode="singleTask" - android:theme="@style/Theme.Leanback.Browse"> + android:theme="@style/Theme.Leanback.Browse" > <intent-filter> <action android:name="android.media.tv.action.VIEW_RECORDING_SCHEDULES" /> + <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="vnd.android.cursor.dir/recorded_program" /> </intent-filter> </activity> - - <activity android:name="com.android.tv.dvr.ui.playback.DvrPlaybackActivity" + <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:theme="@style/Theme.Leanback" > <intent-filter> <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="vnd.android.cursor.item/recorded_program" /> </intent-filter> </activity> - - <activity android:name="com.android.tv.dvr.ui.browse.DvrDetailsActivity" + <activity + android:name="com.android.tv.dvr.ui.browse.DvrDetailsActivity" android:configChanges="keyboard|keyboardHidden" android:theme="@style/Theme.TV.Dvr.Browse.Details" /> - - <activity android:name="com.android.tv.dvr.ui.DvrSeriesSettingsActivity" + <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" + <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" + <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" /> - <provider android:name="com.android.tv.search.LocalSearchProvider" - android:authorities="com.android.tv.search" - android:exported="true" - android:enabled="true" tools:ignore="ExportedContentProvider" - > - <meta-data android:name="SupportedSwitchActionType" android:value="CHANNEL|TVINPUT" /> - </provider> - - <service android:name="com.android.tv.recommendation.NotificationService" - android:exported="false" /> - <service android:name="com.android.tv.recommendation.ChannelPreviewUpdater$ChannelPreviewUpdateService" - android:permission="android.permission.BIND_JOB_SERVICE" /> + <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"> + <receiver android:name="com.android.tv.receiver.BootCompletedReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> - <receiver android:name="com.android.tv.receiver.PackageIntentsReceiver"> + <receiver android:name="com.android.tv.receiver.PackageIntentsReceiver" > <intent-filter> <action android:name="android.intent.action.PACKAGE_ADDED" /> <!-- PACKAGE_CHANGED for package enabled/disabled notification --> <action android:name="android.intent.action.PACKAGE_CHANGED" /> <action android:name="android.intent.action.PACKAGE_REMOVED" /> - <data android:scheme="package"/> + + <data android:scheme="package" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> - </receiver> - <receiver android:name="com.android.tv.livetv.receiver.GlobalKeyReceiver"> - <intent-filter> - <action android:name="android.intent.action.GLOBAL_BUTTON" /> - </intent-filter> - - <!-- Not directly related to GlobalKeyReceiver but needed to be able to provide our - content rating definitions to the system service. --> - <intent-filter> - <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" /> - </intent-filter> - <meta-data android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS" - android:resource="@xml/tv_content_rating_systems" /> - </receiver> - - <!-- USB tuner components definition --> - <activity android:name="com.android.tv.tuner.setup.LiveTvTunerSetupActivity" + </receiver> <!-- System initial setup component definition --> + <activity + android:name="com.android.tv.setup.SystemSetupActivity" android:configChanges="keyboard|keyboardHidden" + android:exported="true" android:label="@string/bt_app_name" android:launchMode="singleInstance" - android:process="com.android.tv.tuner" android:theme="@style/Theme.Setup.GuidedStep" > <intent-filter> - <action android:name="android.intent.action.MAIN" /> - </intent-filter> - </activity> - - <service android:name="com.android.tv.tuner.livetuner.LiveTvTunerTvInputService" - android:enabled="false" - android:process="com.android.tv.tuner" - android:label="@string/bt_app_name" - android:permission="android.permission.BIND_TV_INPUT" > - <intent-filter> - <action android:name="android.media.tv.TvInputService" /> - </intent-filter> - <meta-data android:name="android.media.tv.input" - android:resource="@xml/ut_tvinputservice" /> - </service> - - <provider android:name="com.android.tv.common.CommonPreferenceProvider" - android:authorities="com.android.tv.common.preferences" - android:process="com.android.tv.common" - android:exported="false" /> - <!-- System initial setup component definition --> - <activity android:name="com.android.tv.setup.SystemSetupActivity" - android:configChanges="keyboard|keyboardHidden" - android:label="@string/bt_app_name" - android:launchMode="singleInstance" - android:theme="@style/Theme.Setup.GuidedStep" > - <intent-filter> <action android:name="com.android.tv.action.LAUNCH_SYSTEM_SETUP" /> + <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> - <!-- TunerInputController should be the same process with MainActivity to check status of MainActivity --> - <receiver android:name="com.android.tv.tuner.TunerInputController$IntentReceiver" - android:exported="false"> + <!-- + TunerInputController should be the same process with MainActivity to check status + of MainActivity + --> + <receiver + android:name="com.android.tv.tuner.TunerInputController$IntentReceiver" + android:exported="false" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> @@ -262,19 +279,56 @@ <action android:name="com.android.tv.action.NETWORK_TUNER_ATTACHED" /> <action android:name="com.android.tv.action.NETWORK_TUNER_DETACHED" /> </intent-filter> - </receiver> + </receiver> <!-- DVR --> + <service + android:name="com.android.tv.dvr.recorder.DvrRecordingService" + android:label="@string/dvr_service_name" /> - <!-- DVR --> - <service android:name="com.android.tv.dvr.recorder.DvrRecordingService" android:label="@string/dvr_service_name" /> <receiver android:name="com.android.tv.dvr.recorder.DvrStartRecordingReceiver" /> - <service android:name="com.android.tv.tuner.tvinput.TunerStorageCleanUpService" + <service + android:name="com.android.tv.tuner.tvinput.TunerStorageCleanUpService" + android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE" - android:process="com.android.tv.tuner" - android:exported="false" /> - - <service android:name="com.android.tv.data.epg.EpgFetchService" + android:process="com.android.tv.tuner" /> + <service + android:name="com.android.tv.data.epg.EpgFetchService" android:permission="android.permission.BIND_JOB_SERVICE" /> + <receiver + android:name="com.android.tv.livetv.receiver.GlobalKeyReceiver" + android:exported="true" > + <intent-filter> + <action android:name="android.intent.action.GLOBAL_BUTTON" /> + </intent-filter> + + <!-- + Not directly related to GlobalKeyReceiver but needed to be able to provide our + content rating definitions to the system service. + --> + <intent-filter> + <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" /> + </intent-filter> + + <meta-data + android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS" + android:resource="@xml/tv_content_rating_systems" /> + </receiver> + + <service + android:name="com.android.tv.tuner.livetuner.LiveTvTunerTvInputService" + android:enabled="false" + android:label="@string/bt_app_name" + android:permission="android.permission.BIND_TV_INPUT" + android:process="com.android.tv.tuner" > + <intent-filter> + <action android:name="android.media.tv.TvInputService" /> + </intent-filter> + + <meta-data + android:name="android.media.tv.input" + android:resource="@xml/ut_tvinputservice" /> + </service> </application> -</manifest> + +</manifest>
\ No newline at end of file diff --git a/ResourceManifest.xml b/ResourceManifest.xml new file mode 100644 index 00000000..a859327f --- /dev/null +++ b/ResourceManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tv" xmlns:tools="http://schemas.android.com/tools"> + <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/> + <application /> +</manifest> diff --git a/common/src/com/android/tv/common/CommonConstants.java b/common/src/com/android/tv/common/CommonConstants.java index 44799f6b..ac379d18 100644 --- a/common/src/com/android/tv/common/CommonConstants.java +++ b/common/src/com/android/tv/common/CommonConstants.java @@ -16,51 +16,13 @@ package com.android.tv.common; -import android.media.tv.TvInputInfo; - /** Constants for common use in apps and tests. */ public final class CommonConstants { public static final String BASE_PACKAGE = "com.android.tv"; - /** A constant for the key of the extra data for the app linking intent. */ public static final String EXTRA_APP_LINK_CHANNEL_URI = "app_link_channel_uri"; - /** - * An intent action to launch setup activity of a TV input. The intent should include TV input - * ID in the value of {@link #EXTRA_INPUT_ID}. Optionally, given the value of {@link - * #EXTRA_ACTIVITY_AFTER_COMPLETION}, the activity will be launched after the setup activity - * successfully finishes. - */ - public static final String INTENT_ACTION_INPUT_SETUP = - "com.android.tv.action.LAUNCH_INPUT_SETUP"; - - /** - * A constant of the key to indicate a TV input ID for the intent action {@link - * #INTENT_ACTION_INPUT_SETUP}. - * - * <p>Value type: String - */ - public static final String EXTRA_INPUT_ID = TvInputInfo.EXTRA_INPUT_ID; - - /** - * A constant of the key for intent to launch actual TV input setup activity used with {@link - * #INTENT_ACTION_INPUT_SETUP}. - * - * <p>Value type: Intent (Parcelable) - */ - public static final String EXTRA_SETUP_INTENT = - CommonConstants.BASE_PACKAGE + ".extra.SETUP_INTENT"; - - /** - * A constant of the key to indicate an Activity launch intent for the intent action {@link - * #INTENT_ACTION_INPUT_SETUP}. - * - * <p>Value type: Intent (Parcelable) - */ - public static final String EXTRA_ACTIVITY_AFTER_COMPLETION = - CommonConstants.BASE_PACKAGE + ".intent.extra.ACTIVITY_AFTER_COMPLETION"; - private CommonConstants() {} } diff --git a/common/src/com/android/tv/common/TvContentRatingCache.java b/common/src/com/android/tv/common/TvContentRatingCache.java index 5f91ee3e..cfdb8e4d 100644 --- a/common/src/com/android/tv/common/TvContentRatingCache.java +++ b/common/src/com/android/tv/common/TvContentRatingCache.java @@ -22,6 +22,7 @@ import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import com.android.tv.common.memory.MemoryManageable; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/common/src/com/android/tv/common/actions/InputSetupActionUtils.java b/common/src/com/android/tv/common/actions/InputSetupActionUtils.java new file mode 100644 index 00000000..7ba799ed --- /dev/null +++ b/common/src/com/android/tv/common/actions/InputSetupActionUtils.java @@ -0,0 +1,127 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.common.actions; + +import android.content.Intent; +import android.media.tv.TvInputInfo; +import android.os.Bundle; +import android.support.annotation.Nullable; + +/** Constants and static utilities for the Input Setup Action. */ +public class InputSetupActionUtils { + + /** + * An intent action to launch setup activity of a TV input. + * + * <p>The intent should include TV input ID in the value of {@link #EXTRA_INPUT_ID}. Optionally, + * given the value of {@link #EXTRA_ACTIVITY_AFTER_COMPLETION}, the activity will be launched + * after the setup activity successfully finishes. + */ + public static final String INTENT_ACTION_INPUT_SETUP = + "com.android.tv.action.LAUNCH_INPUT_SETUP"; + /** + * A constant of the key to indicate a TV input ID for the intent action {@link + * #INTENT_ACTION_INPUT_SETUP}. + * + * <p>Value type: String + */ + public static final String EXTRA_INPUT_ID = TvInputInfo.EXTRA_INPUT_ID; + /** + * A constant of the key for intent to launch actual TV input setup activity used with {@link + * #INTENT_ACTION_INPUT_SETUP}. + * + * <p>Value type: Intent (Parcelable) + */ + public static final String EXTRA_SETUP_INTENT = "com.android.tv.extra.SETUP_INTENT"; + /** + * A constant of the key to indicate an Activity launch intent for the intent action {@link + * #INTENT_ACTION_INPUT_SETUP}. + * + * <p>Value type: Intent (Parcelable) + */ + public static final String EXTRA_ACTIVITY_AFTER_COMPLETION = + "com.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION"; + /** + * An intent action to launch setup activity of a TV input. + * + * <p>The intent should include TV input ID in the value of {@link #EXTRA_INPUT_ID}. Optionally, + * given the value of {@link #EXTRA_GOOGLE_ACTIVITY_AFTER_COMPLETION}, the activity will be + * launched after the setup activity successfully finishes. + * + * <p>Value type: Intent (Parcelable) + * + * @deprecated Use {@link #INTENT_ACTION_INPUT_SETUP} instead + */ + @Deprecated + private static final String INTENT_GOOGLE_ACTION_INPUT_SETUP = + "com.google.android.tv.action.LAUNCH_INPUT_SETUP"; + /** + * A Google specific constant of the key for intent to launch actual TV input setup activity + * used with {@link #INTENT_ACTION_INPUT_SETUP}. + * + * <p>Value type: Intent (Parcelable) + * + * @deprecated Use {@link #EXTRA_SETUP_INTENT} instead + */ + @Deprecated + private static final String EXTRA_GOOGLE_SETUP_INTENT = + "com.google.android.tv.extra.SETUP_INTENT"; + /** + * A Google specific constant of the key to indicate an Activity launch intent for the intent + * action {@link #INTENT_ACTION_INPUT_SETUP}. + * + * <p>Value type: Intent (Parcelable) + * + * @deprecated Use {@link #EXTRA_ACTIVITY_AFTER_COMPLETION} instead + */ + @Deprecated + private static final String EXTRA_GOOGLE_ACTIVITY_AFTER_COMPLETION = + "com.google.android.tv.intent.extra.ACTIVITY_AFTER_COMPLETION"; + + public static void removeSetupIntent(Bundle extras) { + extras.remove(EXTRA_SETUP_INTENT); + extras.remove(EXTRA_GOOGLE_SETUP_INTENT); + } + + @Nullable + public static Intent getExtraSetupIntent(Intent intent) { + Bundle extras = intent.getExtras(); + if (extras == null) { + return null; + } + Intent setupIntent = extras.getParcelable(EXTRA_SETUP_INTENT); + return setupIntent != null ? setupIntent : extras.getParcelable(EXTRA_GOOGLE_SETUP_INTENT); + } + + @Nullable + public static Intent getExtraActivityAfter(Intent intent) { + Bundle extras = intent.getExtras(); + if (extras == null) { + return null; + } + Intent setupIntent = extras.getParcelable(EXTRA_ACTIVITY_AFTER_COMPLETION); + return setupIntent != null + ? setupIntent + : extras.getParcelable(EXTRA_GOOGLE_ACTIVITY_AFTER_COMPLETION); + } + + public static boolean hasInputSetupAction(Intent intent) { + String action = intent.getAction(); + return INTENT_ACTION_INPUT_SETUP.equals(action) + || INTENT_GOOGLE_ACTION_INPUT_SETUP.equals(action); + } +} diff --git a/common/src/com/android/tv/common/MemoryManageable.java b/common/src/com/android/tv/common/memory/MemoryManageable.java index f782ccde..3e81fb5e 100644 --- a/common/src/com/android/tv/common/MemoryManageable.java +++ b/common/src/com/android/tv/common/memory/MemoryManageable.java @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.tv.common; +package com.android.tv.common.memory; /** * Interface for the fine-grained memory management. The class which wants to release memory based diff --git a/common/src/com/android/tv/common/util/CommonUtils.java b/common/src/com/android/tv/common/util/CommonUtils.java index 6d9fb76a..00574586 100644 --- a/common/src/com/android/tv/common/util/CommonUtils.java +++ b/common/src/com/android/tv/common/util/CommonUtils.java @@ -23,6 +23,7 @@ import android.os.Build; import android.util.ArraySet; import com.android.tv.common.BuildConfig; import com.android.tv.common.CommonConstants; +import com.android.tv.common.actions.InputSetupActionUtils; import com.android.tv.common.experiments.Experiments; import java.io.File; import java.text.SimpleDateFormat; @@ -58,17 +59,17 @@ public final class CommonUtils { /** * Returns an intent to start the setup activity for the TV input using {@link - * CommonConstants#INTENT_ACTION_INPUT_SETUP}. + * InputSetupActionUtils#INTENT_ACTION_INPUT_SETUP}. */ public static Intent createSetupIntent(Intent originalSetupIntent, String inputId) { if (originalSetupIntent == null) { return null; } Intent setupIntent = new Intent(originalSetupIntent); - if (!CommonConstants.INTENT_ACTION_INPUT_SETUP.equals(originalSetupIntent.getAction())) { - Intent intentContainer = new Intent(CommonConstants.INTENT_ACTION_INPUT_SETUP); - intentContainer.putExtra(CommonConstants.EXTRA_SETUP_INTENT, originalSetupIntent); - intentContainer.putExtra(CommonConstants.EXTRA_INPUT_ID, inputId); + if (!InputSetupActionUtils.hasInputSetupAction(originalSetupIntent)) { + Intent intentContainer = new Intent(InputSetupActionUtils.INTENT_ACTION_INPUT_SETUP); + intentContainer.putExtra(InputSetupActionUtils.EXTRA_SETUP_INTENT, originalSetupIntent); + intentContainer.putExtra(InputSetupActionUtils.EXTRA_INPUT_ID, inputId); setupIntent = intentContainer; } return setupIntent; @@ -76,7 +77,7 @@ public final class CommonUtils { /** * Returns an intent to start the setup activity for this TV input using {@link - * CommonConstants#INTENT_ACTION_INPUT_SETUP}. + * InputSetupActionUtils#INTENT_ACTION_INPUT_SETUP}. */ public static Intent createSetupIntent(TvInputInfo input) { return createSetupIntent(input.createSetupIntent(), input.getId()); diff --git a/common/src/com/android/tv/common/util/ContentUriUtils.java b/common/src/com/android/tv/common/util/ContentUriUtils.java new file mode 100644 index 00000000..4f1a1ef6 --- /dev/null +++ b/common/src/com/android/tv/common/util/ContentUriUtils.java @@ -0,0 +1,29 @@ +package com.android.tv.common.util; + +import android.content.ContentUris; +import android.net.Uri; +import android.util.Log; + +/** Static utils for{@link android.content.ContentUris}. */ +public class ContentUriUtils { + private static final String TAG = "ContentUriUtils"; + + /** + * Converts the last path segment to a long. + * + * <p>This supports a common convention for content URIs where an ID is stored in the last + * segment. + * + * @return the long conversion of the last segment or -1 if the path is empty or there is any + * error + * @see ContentUris#parseId(Uri) + */ + public static long safeParseId(Uri uri) { + try { + return ContentUris.parseId(uri); + } catch (Exception e) { + Log.d(TAG, "Error parsing " + uri, e); + return -1; + } + } +} diff --git a/res/layout/activity_dvr_history.xml b/res/layout/activity_dvr_history.xml new file mode 100644 index 00000000..c44bc8a3 --- /dev/null +++ b/res/layout/activity_dvr_history.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/dvr_history" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/empty_info_screen" + android:layout_width="600dp" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:fontFamily="@string/font" + android:textSize="@dimen/tvview_block_text_size" + android:lineSpacingExtra="@dimen/tvview_block_line_spacing_extra" + android:textColor="@color/tvview_block_text_color" /> + + <FrameLayout android:id="@+id/fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> +</FrameLayout>
\ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index bdc5811e..182171a8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -572,6 +572,8 @@ <!-- Toast message that a new recording schedule of the current program has been created from the user action. --> <string name="dvr_msg_current_program_scheduled">Recording <xliff:g id="programName" example="Big bang theory">%1$s</xliff:g> from now to <xliff:g id="endTime" example="12:30 PM">%2$s</xliff:g></string> + <!-- Description of a card view to show DVR history. [CHAR LIMIT=25] --> + <string name="dvr_history_card_view_title">DVR History</string> <!-- Description of a card view to show full list of scheduled recordings. [CHAR LIMIT=25] --> <string name="dvr_full_schedule_card_view_title">Full schedule</string> <!-- Description of how many following days the schedule list will show. [CHAR LIMIT=25] --> diff --git a/src/com/android/tv/AudioManagerHelper.java b/src/com/android/tv/AudioManagerHelper.java index b0187617..942d431d 100644 --- a/src/com/android/tv/AudioManagerHelper.java +++ b/src/com/android/tv/AudioManagerHelper.java @@ -21,6 +21,7 @@ import android.media.AudioManager; import android.os.Build; import com.android.tv.receiver.AudioCapabilitiesReceiver; import com.android.tv.ui.TunableTvView; +import com.android.tv.ui.TunableTvViewPlayingApi; /** A helper class to help {@link MainActivity} to handle audio-related stuffs. */ class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener { @@ -29,14 +30,14 @@ class AudioManagerHelper implements AudioManager.OnAudioFocusChangeListener { private static final float AUDIO_DUCKING_VOLUME = 0.3f; private final Activity mActivity; - private final TunableTvView mTvView; + private final TunableTvViewPlayingApi mTvView; private final AudioManager mAudioManager; private final AudioCapabilitiesReceiver mAudioCapabilitiesReceiver; private boolean mAc3PassthroughSupported; private int mAudioFocusStatus = AudioManager.AUDIOFOCUS_LOSS; - AudioManagerHelper(Activity activity, TunableTvView tvView) { + AudioManagerHelper(Activity activity, TunableTvViewPlayingApi tvView) { mActivity = activity; mTvView = tvView; mAudioManager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); diff --git a/src/com/android/tv/ChannelTuner.java b/src/com/android/tv/ChannelTuner.java index c6fb329c..8ab145a4 100644 --- a/src/com/android/tv/ChannelTuner.java +++ b/src/com/android/tv/ChannelTuner.java @@ -25,8 +25,8 @@ import android.support.annotation.Nullable; import android.util.ArraySet; import android.util.Log; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.util.TvInputManagerHelper; import java.util.ArrayList; import java.util.Collections; diff --git a/src/com/android/tv/InputSessionManager.java b/src/com/android/tv/InputSessionManager.java index 416dbb68..4f298ed6 100644 --- a/src/com/android/tv/InputSessionManager.java +++ b/src/com/android/tv/InputSessionManager.java @@ -36,7 +36,7 @@ import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.ui.TunableTvView; import com.android.tv.ui.TunableTvView.OnTuneListener; import com.android.tv.util.TvInputManagerHelper; diff --git a/src/com/android/tv/MainActivity.java b/src/com/android/tv/MainActivity.java index edbed077..b5c0b28d 100644 --- a/src/com/android/tv/MainActivity.java +++ b/src/com/android/tv/MainActivity.java @@ -70,24 +70,26 @@ import com.android.tv.analytics.SendConfigInfoRunnable; import com.android.tv.analytics.Tracker; import com.android.tv.common.BuildConfig; import com.android.tv.common.CommonPreferences; -import com.android.tv.common.MemoryManageable; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.TvContentRatingCache; import com.android.tv.common.WeakHandler; import com.android.tv.common.feature.CommonFeatures; +import com.android.tv.common.memory.MemoryManageable; import com.android.tv.common.ui.setup.OnActionClickListener; import com.android.tv.common.util.CommonUtils; +import com.android.tv.common.util.ContentUriUtils; import com.android.tv.common.util.Debug; import com.android.tv.common.util.DurationTimer; import com.android.tv.common.util.PermissionUtils; import com.android.tv.common.util.SystemProperties; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.ChannelImpl; import com.android.tv.data.OnCurrentProgramUpdatedListener; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; import com.android.tv.data.StreamInfo; import com.android.tv.data.WatchedHistoryManager; +import com.android.tv.data.api.Channel; import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dialog.PinDialogFragment; import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener; @@ -127,7 +129,6 @@ import com.android.tv.ui.sidepanel.SideFragment; import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.CaptionSettings; -import com.android.tv.util.ImageCache; import com.android.tv.util.OnboardingUtils; import com.android.tv.util.RecurringRunner; import com.android.tv.util.SetupUtils; @@ -137,6 +138,7 @@ import com.android.tv.util.TvTrackInfoUtils; import com.android.tv.util.Utils; import com.android.tv.util.ViewCache; import com.android.tv.util.account.AccountHelper; +import com.android.tv.util.images.ImageCache; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -616,7 +618,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (TextUtils.equals(input.getId(), currentInputId)) { hideOverlays(); } else { - tuneToChannel(Channel.createPassthroughChannel(input.getId())); + tuneToChannel(ChannelImpl.createPassthroughChannel(input.getId())); } } @@ -1024,7 +1026,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP mChannelTuner.moveToChannel(mChannelTuner.findNearestBrowsableChannel(0)); } else { if (TvContract.isChannelUriForPassthroughInput(channelUri)) { - Channel channel = Channel.createPassthroughChannel(channelUri); + ChannelImpl channel = ChannelImpl.createPassthroughChannel(channelUri); mChannelTuner.moveToChannel(channel); } else { long channelId = ContentUris.parseId(channelUri); @@ -1487,15 +1489,17 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP String programUriString = intent.getStringExtra(SearchManager.EXTRA_DATA_KEY); Uri programUriFromIntent = programUriString == null ? null : Uri.parse(programUriString); - long channelIdFromIntent = ContentUris.parseId(mInitChannelUri); - if (programUriFromIntent != null) { + long channelIdFromIntent = ContentUriUtils.safeParseId(mInitChannelUri); + if (programUriFromIntent != null && channelIdFromIntent != Channel.INVALID_ID) { new AsyncQueryProgramTask( - TvSingletons.getSingletons(this).getDbExecutor(), - getContentResolver(), - programUriFromIntent, - Program.PROJECTION, - null, null, null, - channelIdFromIntent) + TvSingletons.getSingletons(this).getDbExecutor(), + getContentResolver(), + programUriFromIntent, + Program.PROJECTION, + null, + null, + null, + channelIdFromIntent) .executeOnDbThread(); } if (mTuneParams == null) { @@ -1554,8 +1558,14 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP private class AsyncQueryProgramTask extends AsyncDbTask.AsyncQueryTask<Program> { private final long mChannelIdFromIntent; - public AsyncQueryProgramTask(Executor executor, ContentResolver contentResolver, Uri uri, - String[] projection, String selection, String[] selectionArgs, String orderBy, + public AsyncQueryProgramTask( + Executor executor, + ContentResolver contentResolver, + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String orderBy, long channelId) { super(executor, contentResolver, uri, projection, selection, selectionArgs, orderBy); mChannelIdFromIntent = channelId; @@ -1592,9 +1602,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP && scheduledRecording == null && mDvrManager.isProgramRecordable(program)) { DvrUiHelper.requestRecordingFutureProgram( - MainActivity.this, - program, - false); + MainActivity.this, program, false); } else { DvrUiHelper.showProgramInfoDialog(MainActivity.this, program); } @@ -1603,6 +1611,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP } } } + private void stopTv() { stopTv(null, false); } @@ -1734,7 +1743,7 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP // For every tune, we need to inform the tuned channel or input to a user, // if Talkback is turned on. sendAccessibilityText( - !mChannelTuner.isCurrentChannelPassthrough() + mChannelTuner.isCurrentChannelPassthrough() ? Utils.loadLabel( this, mTvInputManagerHelper.getTvInputInfo(channel.getInputId())) : channel.getDisplayText()); @@ -2826,7 +2835,8 @@ public class MainActivity extends Activity implements OnActionClickListener, OnP if (channel == null) { return; } - Channel currentChannel = mChannelDataManager.getChannel(ContentUris.parseId(channel)); + Channel currentChannel = + mChannelDataManager.getChannel(ContentUriUtils.safeParseId(channel)); if (currentChannel == null) { Log.e( TAG, diff --git a/src/com/android/tv/MainActivityWrapper.java b/src/com/android/tv/MainActivityWrapper.java index 6a995cf1..6cecb436 100644 --- a/src/com/android/tv/MainActivityWrapper.java +++ b/src/com/android/tv/MainActivityWrapper.java @@ -20,7 +20,7 @@ import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.ArraySet; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import java.util.Set; /** diff --git a/src/com/android/tv/MediaSessionWrapper.java b/src/com/android/tv/MediaSessionWrapper.java index b3472ba5..43cd74dd 100644 --- a/src/com/android/tv/MediaSessionWrapper.java +++ b/src/com/android/tv/MediaSessionWrapper.java @@ -31,10 +31,10 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; -import com.android.tv.data.Channel; import com.android.tv.data.Program; -import com.android.tv.util.ImageLoader; +import com.android.tv.data.api.Channel; import com.android.tv.util.Utils; +import com.android.tv.util.images.ImageLoader; /** * A wrapper class for {@link MediaSession} to support common operations on media sessions for diff --git a/src/com/android/tv/SelectInputActivity.java b/src/com/android/tv/SelectInputActivity.java index 4487fbe2..56747044 100644 --- a/src/com/android/tv/SelectInputActivity.java +++ b/src/com/android/tv/SelectInputActivity.java @@ -23,7 +23,7 @@ import android.media.tv.TvInputInfo; import android.net.Uri; import android.os.Bundle; import android.view.KeyEvent; -import com.android.tv.data.Channel; +import com.android.tv.data.ChannelImpl; import com.android.tv.ui.SelectInputView; import com.android.tv.ui.SelectInputView.OnInputSelectedCallback; import com.android.tv.util.Utils; @@ -67,7 +67,8 @@ public class SelectInputActivity extends Activity { if (channelUriString != null) { Uri channelUri = Uri.parse(channelUriString); if (TvContract.isChannelUriForPassthroughInput(channelUri)) { - mSelectInputView.setCurrentChannel(Channel.createPassthroughChannel(channelUri)); + mSelectInputView.setCurrentChannel( + ChannelImpl.createPassthroughChannel(channelUri)); } // No need to set the tuner channel because it's the default selection. } diff --git a/src/com/android/tv/SetupPassthroughActivity.java b/src/com/android/tv/SetupPassthroughActivity.java index 83885df4..199ea51d 100644 --- a/src/com/android/tv/SetupPassthroughActivity.java +++ b/src/com/android/tv/SetupPassthroughActivity.java @@ -26,8 +26,8 @@ import android.os.Handler; import android.os.Looper; import android.support.annotation.MainThread; import android.util.Log; -import com.android.tv.common.CommonConstants; import com.android.tv.common.SoftPreconditions; +import com.android.tv.common.actions.InputSetupActionUtils; import com.android.tv.common.experiments.Experiments; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.ChannelDataManager.Listener; @@ -64,11 +64,10 @@ public class SetupPassthroughActivity extends Activity { TvSingletons tvSingletons = TvSingletons.getSingletons(this); TvInputManagerHelper inputManager = tvSingletons.getTvInputManagerHelper(); Intent intent = getIntent(); - String inputId = intent.getStringExtra(CommonConstants.EXTRA_INPUT_ID); + String inputId = intent.getStringExtra(InputSetupActionUtils.EXTRA_INPUT_ID); mTvInputInfo = inputManager.getTvInputInfo(inputId); mEpgInputWhiteList = new EpgInputWhiteList(tvSingletons.getRemoteConfig()); - mActivityAfterCompletion = - intent.getParcelableExtra(CommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION); + mActivityAfterCompletion = InputSetupActionUtils.getExtraActivityAfter(intent); boolean needToFetchEpg = mTvInputInfo != null && Utils.isInternalTvInput(this, mTvInputInfo.getId()) @@ -79,7 +78,7 @@ public class SetupPassthroughActivity extends Activity { } if (savedInstanceState == null) { SoftPreconditions.checkArgument( - CommonConstants.INTENT_ACTION_INPUT_SETUP.equals(intent.getAction()), + InputSetupActionUtils.hasInputSetupAction(intent), TAG, "Unsupported action %s", intent.getAction()); @@ -94,8 +93,7 @@ public class SetupPassthroughActivity extends Activity { finish(); return; } - Intent setupIntent = - intent.getExtras().getParcelable(CommonConstants.EXTRA_SETUP_INTENT); + Intent setupIntent = InputSetupActionUtils.getExtraSetupIntent(intent); if (DEBUG) Log.d(TAG, "Setup activity launch intent: " + setupIntent); if (setupIntent == null) { Log.w(TAG, "The input (" + mTvInputInfo.getId() + ") doesn't have setup."); @@ -107,7 +105,7 @@ public class SetupPassthroughActivity extends Activity { // If EXTRA_SETUP_INTENT is not removed, an infinite recursion happens during // setupIntent.putExtras(intent.getExtras()). Bundle extras = intent.getExtras(); - extras.remove(CommonConstants.EXTRA_SETUP_INTENT); + InputSetupActionUtils.removeSetupIntent(extras); setupIntent.putExtras(extras); try { startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY); diff --git a/src/com/android/tv/TimeShiftManager.java b/src/com/android/tv/TimeShiftManager.java index a72dd8c7..bb3574d7 100644 --- a/src/com/android/tv/TimeShiftManager.java +++ b/src/com/android/tv/TimeShiftManager.java @@ -30,12 +30,12 @@ import android.util.Range; import com.android.tv.analytics.Tracker; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.WeakHandler; -import com.android.tv.data.Channel; import com.android.tv.data.OnCurrentProgramUpdatedListener; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.ui.TunableTvView; -import com.android.tv.ui.TunableTvView.TimeShiftListener; +import com.android.tv.ui.TunableTvViewPlayingApi.TimeShiftListener; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.TimeShiftUtils; import com.android.tv.util.Utils; @@ -449,7 +449,7 @@ public class TimeShiftManager { SoftPreconditions.checkState(isAvailable(), TAG, "Time shift is not available"); SoftPreconditions.checkState(mCurrentPositionMediator.mCurrentPositionMs != INVALID_TIME); Program currentProgram = getProgramAt(mCurrentPositionMediator.mCurrentPositionMs); - if (!Program.isValid(currentProgram)) { + if (!Program.isProgramValid(currentProgram)) { currentProgram = null; } if (!Objects.equals(mCurrentProgram, currentProgram)) { diff --git a/src/com/android/tv/TvFeatures.java b/src/com/android/tv/TvFeatures.java index f8125814..d2cf76e7 100644 --- a/src/com/android/tv/TvFeatures.java +++ b/src/com/android/tv/TvFeatures.java @@ -49,7 +49,8 @@ public final class TvFeatures extends CommonFeatures { /** When enabled use system setting for turning on analytics. */ public static final Feature ANALYTICS_OPT_IN = ExperimentFeature.from(Experiments.ENABLE_ANALYTICS_VIA_CHECKBOX); - + /** When enabled shows a list of failed recordings */ + public static final Feature DVR_FAILED_LIST = ENG_ONLY_FEATURE; /** * Analytics that include sensitive information such as channel or program identifiers. * diff --git a/src/com/android/tv/analytics/SendChannelStatusRunnable.java b/src/com/android/tv/analytics/SendChannelStatusRunnable.java index 601e82f7..4a84434c 100644 --- a/src/com/android/tv/analytics/SendChannelStatusRunnable.java +++ b/src/com/android/tv/analytics/SendChannelStatusRunnable.java @@ -20,8 +20,8 @@ import android.content.Context; import android.os.Handler; import android.os.Looper; import android.support.annotation.MainThread; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.util.RecurringRunner; import java.util.List; import java.util.concurrent.TimeUnit; diff --git a/src/com/android/tv/analytics/StubTracker.java b/src/com/android/tv/analytics/StubTracker.java index 4a926d58..e11b91c2 100644 --- a/src/com/android/tv/analytics/StubTracker.java +++ b/src/com/android/tv/analytics/StubTracker.java @@ -18,7 +18,7 @@ package com.android.tv.analytics; import android.support.annotation.VisibleForTesting; import com.android.tv.TimeShiftManager; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; /** A implementation of Tracker that does nothing. */ @VisibleForTesting diff --git a/src/com/android/tv/analytics/Tracker.java b/src/com/android/tv/analytics/Tracker.java index 8c72e3b4..0fcef5dc 100644 --- a/src/com/android/tv/analytics/Tracker.java +++ b/src/com/android/tv/analytics/Tracker.java @@ -17,7 +17,7 @@ package com.android.tv.analytics; import com.android.tv.TimeShiftManager; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; /** Interface for sending user activity for analysis. */ public interface Tracker { diff --git a/src/com/android/tv/app/LiveTvApplication.java b/src/com/android/tv/app/LiveTvApplication.java index f6ac2441..461331d5 100644 --- a/src/com/android/tv/app/LiveTvApplication.java +++ b/src/com/android/tv/app/LiveTvApplication.java @@ -25,6 +25,7 @@ import com.android.tv.analytics.Analytics; import com.android.tv.analytics.StubAnalytics; import com.android.tv.analytics.Tracker; import com.android.tv.common.CommonConstants; +import com.android.tv.common.actions.InputSetupActionUtils; import com.android.tv.common.config.DefaultConfigManager; import com.android.tv.common.config.api.RemoteConfig; import com.android.tv.common.experiments.ExperimentLoader; @@ -110,10 +111,10 @@ public class LiveTvApplication extends TvApplication { Intent intent = CommonUtils.createSetupIntent( new Intent(context, LiveTvTunerSetupActivity.class), mEmbeddedInputId); - intent.putExtra(CommonConstants.EXTRA_INPUT_ID, mEmbeddedInputId); + intent.putExtra(InputSetupActionUtils.EXTRA_INPUT_ID, mEmbeddedInputId); Intent tvActivityIntent = new Intent(); tvActivityIntent.setComponent(new ComponentName(context, TV_ACTIVITY_CLASS_NAME)); - intent.putExtra(CommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION, tvActivityIntent); + intent.putExtra(InputSetupActionUtils.EXTRA_ACTIVITY_AFTER_COMPLETION, tvActivityIntent); return intent; } diff --git a/src/com/android/tv/data/ChannelDataManager.java b/src/com/android/tv/data/ChannelDataManager.java index 90e7d60a..1dfcf125 100644 --- a/src/com/android/tv/data/ChannelDataManager.java +++ b/src/com/android/tv/data/ChannelDataManager.java @@ -43,6 +43,7 @@ import com.android.tv.common.SoftPreconditions; import com.android.tv.common.WeakHandler; import com.android.tv.common.util.PermissionUtils; import com.android.tv.common.util.SharedPreferencesUtils; +import com.android.tv.data.api.Channel; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -82,7 +83,7 @@ public class ChannelDataManager { // Use container class to support multi-thread safety. This value can be set only on the main // thread. private volatile UnmodifiableChannelData mData = new UnmodifiableChannelData(); - private final Channel.DefaultComparator mChannelComparator; + private final ChannelImpl.DefaultComparator mChannelComparator; private final Handler mHandler; private final Set<Long> mBrowsableUpdateChannelIds = new HashSet<>(); @@ -162,7 +163,7 @@ public class ChannelDataManager { mInputManager = inputManager; mDbExecutor = executor; mContentResolver = contentResolver; - mChannelComparator = new Channel.DefaultComparator(context, inputManager); + mChannelComparator = new ChannelImpl.DefaultComparator(context, inputManager); // Detect duplicate channels while sorting. mChannelComparator.setDetectDuplicatesEnabled(true); mHandler = new ChannelDataManagerHandler(this); diff --git a/src/com/android/tv/data/Channel.java b/src/com/android/tv/data/ChannelImpl.java index 1204a49f..703f69c9 100644 --- a/src/com/android/tv/data/Channel.java +++ b/src/com/android/tv/data/ChannelImpl.java @@ -30,9 +30,10 @@ import android.text.TextUtils; import android.util.Log; import com.android.tv.common.CommonConstants; import com.android.tv.common.util.CommonUtils; -import com.android.tv.util.ImageLoader; +import com.android.tv.data.api.Channel; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; +import com.android.tv.util.images.ImageLoader; import java.net.URISyntaxException; import java.util.Comparator; import java.util.HashMap; @@ -40,13 +41,8 @@ import java.util.Map; import java.util.Objects; /** A convenience class to create and insert channel entries into the database. */ -public final class Channel { - private static final String TAG = "Channel"; - - public static final long INVALID_ID = -1; - public static final int LOAD_IMAGE_TYPE_CHANNEL_LOGO = 1; - public static final int LOAD_IMAGE_TYPE_APP_LINK_ICON = 2; - public static final int LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART = 3; +public final class ChannelImpl implements Channel { + private static final String TAG = "ChannelImpl"; /** Compares the channel numbers of channels which belong to the same input. */ public static final Comparator<Channel> CHANNEL_NUMBER_COMPARATOR = @@ -57,27 +53,11 @@ public final class Channel { } }; - /** - * When a TIS doesn't provide any information about app link, and it doesn't have a leanback - * launch intent, there will be no app link card for the TIS. - */ - public static final int APP_LINK_TYPE_NONE = -1; - /** - * When a TIS provide a specific app link information, the app link card will be {@code - * APP_LINK_TYPE_CHANNEL} which contains all the provided information. - */ - public static final int APP_LINK_TYPE_CHANNEL = 1; - /** - * When a TIS doesn't provide a specific app link information, but the app has a leanback launch - * intent, the app link card will be {@code APP_LINK_TYPE_APP} which launches the application. - */ - public static final int APP_LINK_TYPE_APP = 2; - private static final int APP_LINK_TYPE_NOT_SET = 0; private static final String INVALID_PACKAGE_NAME = "packageName"; public static final String[] PROJECTION = { - // Columns must match what is read in Channel.fromCursor() + // Columns must match what is read in ChannelImpl.fromCursor() TvContract.Channels._ID, TvContract.Channels.COLUMN_PACKAGE_NAME, TvContract.Channels.COLUMN_INPUT_ID, @@ -97,17 +77,14 @@ public final class Channel { TvContract.Channels.COLUMN_INTERNAL_PROVIDER_FLAG2, // Only used in bundled input }; - /** Channel number delimiter between major and minor parts. */ - public static final char CHANNEL_NUMBER_DELIMITER = '-'; - /** - * Creates {@code Channel} object from cursor. + * Creates {@code ChannelImpl} object from cursor. * * <p>The query that created the cursor MUST use {@link #PROJECTION} */ - public static Channel fromCursor(Cursor cursor) { + public static ChannelImpl fromCursor(Cursor cursor) { // Columns read must match the order of {@link #PROJECTION} - Channel channel = new Channel(); + ChannelImpl channel = new ChannelImpl(); int index = 0; channel.mId = cursor.getLong(index++); channel.mPackageName = Utils.intern(cursor.getString(index++)); @@ -175,14 +152,16 @@ public final class Channel { private boolean mChannelLogoExist; - private Channel() { + private ChannelImpl() { // Do nothing. } + @Override public long getId() { return mId; } + @Override public Uri getUri() { if (isPassthrough()) { return TvContract.buildChannelUriForPassthroughInput(mInputId); @@ -191,35 +170,43 @@ public final class Channel { } } + @Override public String getPackageName() { return mPackageName; } + @Override public String getInputId() { return mInputId; } + @Override public String getType() { return mType; } + @Override public String getDisplayNumber() { return mDisplayNumber; } + @Override @Nullable public String getDisplayName() { return mDisplayName; } + @Override public String getDescription() { return mDescription; } + @Override public String getVideoFormat() { return mVideoFormat; } + @Override public boolean isPassthrough() { return mIsPassthrough; } @@ -228,42 +215,51 @@ public final class Channel { * Gets identification text for displaying or debugging. It's made from Channels' display number * plus their display name. */ + @Override public String getDisplayText() { return TextUtils.isEmpty(mDisplayName) ? mDisplayNumber : mDisplayNumber + " " + mDisplayName; } + @Override public String getAppLinkText() { return mAppLinkText; } + @Override public int getAppLinkColor() { return mAppLinkColor; } + @Override public String getAppLinkIconUri() { return mAppLinkIconUri; } + @Override public String getAppLinkPosterArtUri() { return mAppLinkPosterArtUri; } + @Override public String getAppLinkIntentUri() { return mAppLinkIntentUri; } /** Returns channel logo uri which is got from cloud, it's used only for ChannelLogoFetcher. */ + @Override public String getLogoUri() { return mLogoUri; } + @Override public boolean isRecordingProhibited() { return mRecordingProhibited; } /** Checks whether this channel is physical tuner channel or not. */ + @Override public boolean isPhysicalTunerChannel() { return !TextUtils.isEmpty(mType) && !TvContract.Channels.TYPE_OTHER.equals(mType); } @@ -271,10 +267,10 @@ public final class Channel { /** Checks if two channels equal by checking ids. */ @Override public boolean equals(Object o) { - if (!(o instanceof Channel)) { + if (!(o instanceof ChannelImpl)) { return false; } - Channel other = (Channel) o; + ChannelImpl other = (ChannelImpl) o; // All pass-through TV channels have INVALID_ID value for mId. return mId == other.mId && TextUtils.equals(mInputId, other.mInputId) @@ -286,15 +282,18 @@ public final class Channel { return Objects.hash(mId, mInputId, mIsPassthrough); } + @Override public boolean isBrowsable() { return mBrowsable; } /** Checks whether this channel is searchable or not. */ + @Override public boolean isSearchable() { return mSearchable; } + @Override public boolean isLocked() { return mLocked; } @@ -317,23 +316,24 @@ public final class Channel { * channels have same logos. It also excludes browsable and locked, because two fields are * changed by TV app. */ + @Override public boolean hasSameReadOnlyInfo(Channel other) { return other != null - && Objects.equals(mId, other.mId) - && Objects.equals(mPackageName, other.mPackageName) - && Objects.equals(mInputId, other.mInputId) - && Objects.equals(mType, other.mType) - && Objects.equals(mDisplayNumber, other.mDisplayNumber) - && Objects.equals(mDisplayName, other.mDisplayName) - && Objects.equals(mDescription, other.mDescription) - && Objects.equals(mVideoFormat, other.mVideoFormat) - && mIsPassthrough == other.mIsPassthrough - && Objects.equals(mAppLinkText, other.mAppLinkText) - && mAppLinkColor == other.mAppLinkColor - && Objects.equals(mAppLinkIconUri, other.mAppLinkIconUri) - && Objects.equals(mAppLinkPosterArtUri, other.mAppLinkPosterArtUri) - && Objects.equals(mAppLinkIntentUri, other.mAppLinkIntentUri) - && Objects.equals(mRecordingProhibited, other.mRecordingProhibited); + && Objects.equals(mId, other.getId()) + && Objects.equals(mPackageName, other.getPackageName()) + && Objects.equals(mInputId, other.getInputId()) + && Objects.equals(mType, other.getType()) + && Objects.equals(mDisplayNumber, other.getDisplayNumber()) + && Objects.equals(mDisplayName, other.getDisplayName()) + && Objects.equals(mDescription, other.getDescription()) + && Objects.equals(mVideoFormat, other.getVideoFormat()) + && mIsPassthrough == other.isPassthrough() + && Objects.equals(mAppLinkText, other.getAppLinkText()) + && mAppLinkColor == other.getAppLinkColor() + && Objects.equals(mAppLinkIconUri, other.getAppLinkIconUri()) + && Objects.equals(mAppLinkPosterArtUri, other.getAppLinkPosterArtUri()) + && Objects.equals(mAppLinkIntentUri, other.getAppLinkIntentUri()) + && Objects.equals(mRecordingProhibited, other.isRecordingProhibited()); } @Override @@ -370,7 +370,37 @@ public final class Channel { + "}"; } - void copyFrom(Channel other) { + @Override + public void copyFrom(Channel channel) { + if (channel instanceof ChannelImpl) { + copyFrom((ChannelImpl) channel); + } else { + // copy what we can + mId = channel.getId(); + mPackageName = channel.getPackageName(); + mInputId = channel.getInputId(); + mType = channel.getType(); + mDisplayNumber = channel.getDisplayNumber(); + mDisplayName = channel.getDisplayName(); + mDescription = channel.getDescription(); + mVideoFormat = channel.getVideoFormat(); + mIsPassthrough = channel.isPassthrough(); + mBrowsable = channel.isBrowsable(); + mSearchable = channel.isSearchable(); + mLocked = channel.isLocked(); + mAppLinkText = channel.getAppLinkText(); + mAppLinkColor = channel.getAppLinkColor(); + mAppLinkIconUri = channel.getAppLinkIconUri(); + mAppLinkPosterArtUri = channel.getAppLinkPosterArtUri(); + mAppLinkIntentUri = channel.getAppLinkIntentUri(); + mRecordingProhibited = channel.isRecordingProhibited(); + mChannelLogoExist = channel.channelLogoExists(); + } + } + + @SuppressWarnings("ReferenceEquality") + public void copyFrom(ChannelImpl channel) { + ChannelImpl other = (ChannelImpl) channel; if (this == other) { return; } @@ -398,7 +428,7 @@ public final class Channel { } /** Creates a channel for a passthrough TV input. */ - public static Channel createPassthroughChannel(Uri uri) { + public static ChannelImpl createPassthroughChannel(Uri uri) { if (!TvContract.isChannelUriForPassthroughInput(uri)) { throw new IllegalArgumentException("URI is not a passthrough channel URI"); } @@ -407,24 +437,24 @@ public final class Channel { } /** Creates a channel for a passthrough TV input with {@code inputId}. */ - public static Channel createPassthroughChannel(String inputId) { + public static ChannelImpl createPassthroughChannel(String inputId) { return new Builder().setInputId(inputId).setPassthrough(true).build(); } /** Checks whether the channel is valid or not. */ public static boolean isValid(Channel channel) { - return channel != null && (channel.mId != INVALID_ID || channel.mIsPassthrough); + return channel != null && (channel.getId() != INVALID_ID || channel.isPassthrough()); } /** - * Builder class for {@code Channel}. Suppress using this outside of ChannelDataManager so + * Builder class for {@code ChannelImpl}. Suppress using this outside of ChannelDataManager so * Channels could be managed by ChannelDataManager. */ public static final class Builder { - private final Channel mChannel; + private final ChannelImpl mChannel; public Builder() { - mChannel = new Channel(); + mChannel = new ChannelImpl(); // Fill initial data. mChannel.mId = INVALID_ID; mChannel.mPackageName = INVALID_PACKAGE_NAME; @@ -438,7 +468,7 @@ public final class Channel { } public Builder(Channel other) { - mChannel = new Channel(); + mChannel = new ChannelImpl(); mChannel.copyFrom(other); } @@ -539,8 +569,8 @@ public final class Channel { return this; } - public Channel build() { - Channel channel = new Channel(); + public ChannelImpl build() { + ChannelImpl channel = new ChannelImpl(); channel.copyFrom(mChannel); return channel; } @@ -562,8 +592,8 @@ public final class Channel { * * @param context A context. * @param type The type of bitmap which will be loaded. It should be one of follows: {@link - * #LOAD_IMAGE_TYPE_CHANNEL_LOGO}, {@link #LOAD_IMAGE_TYPE_APP_LINK_ICON}, or {@link - * #LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART}. + * Channel#LOAD_IMAGE_TYPE_CHANNEL_LOGO}, {@link Channel#LOAD_IMAGE_TYPE_APP_LINK_ICON}, or + * {@link Channel#LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART}. * @param maxWidth The max width of the loaded bitmap. * @param maxHeight The max height of the loaded bitmap. * @param callback A callback which will be called after the loading finished. @@ -583,7 +613,8 @@ public final class Channel { * Sets if the channel logo exists. This method should be only called from {@link * ChannelDataManager}. */ - void setChannelLogoExist(boolean exist) { + @Override + public void setChannelLogoExist(boolean exist) { mChannelLogoExist = exist; } @@ -593,10 +624,11 @@ public final class Channel { } /** - * Returns the type of app link for this channel. It returns {@link #APP_LINK_TYPE_CHANNEL} if - * the channel has a non null app link text and a valid app link intent, it returns {@link - * #APP_LINK_TYPE_APP} if the input service which holds the channel has leanback launch intent, - * and it returns {@link #APP_LINK_TYPE_NONE} otherwise. + * Returns the type of app link for this channel. It returns {@link + * Channel#APP_LINK_TYPE_CHANNEL} if the channel has a non null app link text and a valid app + * link intent, it returns {@link Channel#APP_LINK_TYPE_APP} if the input service which holds + * the channel has leanback launch intent, and it returns {@link Channel#APP_LINK_TYPE_NONE} + * otherwise. */ public int getAppLinkType(Context context) { if (mAppLinkType == APP_LINK_TYPE_NOT_SET) { @@ -607,7 +639,7 @@ public final class Channel { /** * Returns the app link intent for this channel. If the type of app link is {@link - * #APP_LINK_TYPE_NONE}, it returns {@code null}. + * Channel#APP_LINK_TYPE_NONE}, it returns {@code null}. */ public Intent getAppLinkIntent(Context context) { if (mAppLinkType == APP_LINK_TYPE_NOT_SET) { @@ -660,6 +692,17 @@ public final class Channel { return null; } + /** + * Default Channel ordering. + * + * <p>Ordering + * <li>{@link TvInputManagerHelper#isPartnerInput(String)} + * <li>{@link #getInputLabelForChannel(Channel)} + * <li>{@link #getInputId()} + * <li>{@link ChannelNumber#compare(String, String)} + * <li> + * </ol> + */ public static class DefaultComparator implements Comparator<Channel> { private final Context mContext; private final TvInputManagerHelper mInputManager; @@ -675,6 +718,7 @@ public final class Channel { mDetectDuplicatesEnabled = detectDuplicatesEnabled; } + @SuppressWarnings("ReferenceEquality") @Override public int compare(Channel lhs, Channel rhs) { if (lhs == rhs) { diff --git a/src/com/android/tv/data/ChannelLogoFetcher.java b/src/com/android/tv/data/ChannelLogoFetcher.java index bda78693..89d1e36c 100644 --- a/src/com/android/tv/data/ChannelLogoFetcher.java +++ b/src/com/android/tv/data/ChannelLogoFetcher.java @@ -30,8 +30,9 @@ import android.text.TextUtils; import android.util.Log; import com.android.tv.common.util.PermissionUtils; import com.android.tv.common.util.SharedPreferencesUtils; -import com.android.tv.util.BitmapUtils; -import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; +import com.android.tv.data.api.Channel; +import com.android.tv.util.images.BitmapUtils; +import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; diff --git a/src/com/android/tv/data/ChannelNumber.java b/src/com/android/tv/data/ChannelNumber.java index 1623b33d..afdcc580 100644 --- a/src/com/android/tv/data/ChannelNumber.java +++ b/src/com/android/tv/data/ChannelNumber.java @@ -20,6 +20,7 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.view.KeyEvent; import com.android.tv.common.util.StringUtils; +import com.android.tv.data.api.Channel; import java.util.Objects; /** A convenience class to handle channel number. */ @@ -120,8 +121,8 @@ public final class ChannelNumber implements Comparable<ChannelNumber> { * Returns the ChannelNumber instance. * * <p>Note that all the channel number argument should be normalized by {@link - * Channel#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager} are - * already normalized. + * ChannelImpl#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager} + * are already normalized. */ public static ChannelNumber parseChannelNumber(String number) { if (number == null) { @@ -151,8 +152,8 @@ public final class ChannelNumber implements Comparable<ChannelNumber> { * Compares the channel numbers. * * <p>Note that all the channel number arguments should be normalized by {@link - * Channel#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager} are - * already normalized. + * ChannelImpl#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager} + * are already normalized. */ public static int compare(String lhs, String rhs) { ChannelNumber lhsNumber = parseChannelNumber(lhs); diff --git a/src/com/android/tv/data/PreviewProgramContent.java b/src/com/android/tv/data/PreviewProgramContent.java index 252092b0..b5156408 100644 --- a/src/com/android/tv/data/PreviewProgramContent.java +++ b/src/com/android/tv/data/PreviewProgramContent.java @@ -23,6 +23,7 @@ import android.support.media.tv.TvContractCompat; import android.text.TextUtils; import android.util.Pair; import com.android.tv.TvSingletons; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.data.RecordedProgram; import java.util.Objects; @@ -104,6 +105,7 @@ public class PreviewProgramContent { private PreviewProgramContent() {} + @SuppressWarnings("ReferenceEquality") public void copyFrom(PreviewProgramContent other) { if (this == other) { return; diff --git a/src/com/android/tv/data/Program.java b/src/com/android/tv/data/Program.java index 30a3033e..2c64cdbb 100644 --- a/src/com/android/tv/data/Program.java +++ b/src/com/android/tv/data/Program.java @@ -36,8 +36,9 @@ import com.android.tv.common.BuildConfig; import com.android.tv.common.TvContentRatingCache; import com.android.tv.common.util.CollectionUtils; import com.android.tv.common.util.CommonUtils; -import com.android.tv.util.ImageLoader; +import com.android.tv.data.api.Channel; import com.android.tv.util.Utils; +import com.android.tv.util.images.ImageLoader; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -236,7 +237,7 @@ public final class Program extends BaseProgram implements Comparable<Program>, P } /** Returns {@code true} if the program is valid and {@code false} otherwise. */ - public static boolean isValid(Program program) { + public static boolean isProgramValid(Program program) { return program != null && program.isValid(); } diff --git a/src/com/android/tv/data/ProgramDataManager.java b/src/com/android/tv/data/ProgramDataManager.java index 524d23e3..4631806c 100644 --- a/src/com/android/tv/data/ProgramDataManager.java +++ b/src/com/android/tv/data/ProgramDataManager.java @@ -34,11 +34,12 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.LruCache; import com.android.tv.TvSingletons; -import com.android.tv.common.MemoryManageable; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.config.api.RemoteConfig; import com.android.tv.common.config.api.RemoteConfigValue; +import com.android.tv.common.memory.MemoryManageable; import com.android.tv.common.util.Clock; +import com.android.tv.data.api.Channel; import com.android.tv.util.AsyncDbTask; import com.android.tv.util.MultiLongSparseArray; import com.android.tv.util.Utils; @@ -304,10 +305,11 @@ public class ProgramDataManager implements MemoryManageable { } /** - * Returns the index of program that is played at the specified time. - * If there isn't, return the first program among programs that starts after the given time - * if returnNextProgram is {@code true}. - */ + * Returns the index of program that is played at the specified time. + * + * <p>If there isn't, return the first program among programs that starts after the given time + * if returnNextProgram is {@code true}. + */ private int getProgramIndexAt(List<Program> programs, long time) { Program key = mZeroLengthProgramCache.get(time); if (key == null) { @@ -391,7 +393,7 @@ public class ProgramDataManager implements MemoryManageable { private void removePreviousProgramsAndUpdateCurrentProgramInCache( long channelId, Program currentProgram) { SoftPreconditions.checkState(mPrefetchEnabled, TAG, "Prefetch is disabled."); - if (!Program.isValid(currentProgram)) { + if (!Program.isProgramValid(currentProgram)) { return; } ArrayList<Program> cachedPrograms = mChannelIdProgramCache.remove(channelId); diff --git a/src/com/android/tv/data/StreamInfo.java b/src/com/android/tv/data/StreamInfo.java index 1f84235d..e4237bf4 100644 --- a/src/com/android/tv/data/StreamInfo.java +++ b/src/com/android/tv/data/StreamInfo.java @@ -17,6 +17,7 @@ package com.android.tv.data; import android.media.tv.TvContentRating; +import com.android.tv.data.api.Channel; public interface StreamInfo { int VIDEO_DEFINITION_LEVEL_UNKNOWN = 0; diff --git a/src/com/android/tv/data/WatchedHistoryManager.java b/src/com/android/tv/data/WatchedHistoryManager.java index 7dc48a75..7187efd1 100644 --- a/src/com/android/tv/data/WatchedHistoryManager.java +++ b/src/com/android/tv/data/WatchedHistoryManager.java @@ -28,6 +28,7 @@ import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; import android.util.Log; import com.android.tv.common.util.SharedPreferencesUtils; +import com.android.tv.data.api.Channel; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/src/com/android/tv/data/api/Channel.java b/src/com/android/tv/data/api/Channel.java new file mode 100644 index 00000000..496331cf --- /dev/null +++ b/src/com/android/tv/data/api/Channel.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tv.data.api; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.Nullable; +import com.android.tv.util.images.ImageLoader.ImageLoaderCallback; + +/** + * Interface for {@link com.android.tv.data.ChannelImpl}. + * + * <p><b>NOTE</b> Normally you should not use an interface for a data object like {@code + * ChannelImpl}, however there are many circular dependencies. An interface is the easiest way to + * break the cycles. + */ +public interface Channel { + + long INVALID_ID = -1; + int LOAD_IMAGE_TYPE_CHANNEL_LOGO = 1; + int LOAD_IMAGE_TYPE_APP_LINK_ICON = 2; + int LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART = 3; + /** + * When a TIS doesn't provide any information about app link, and it doesn't have a leanback + * launch intent, there will be no app link card for the TIS. + */ + int APP_LINK_TYPE_NONE = -1; + /** + * When a TIS provide a specific app link information, the app link card will be {@code + * APP_LINK_TYPE_CHANNEL} which contains all the provided information. + */ + int APP_LINK_TYPE_CHANNEL = 1; + /** + * When a TIS doesn't provide a specific app link information, but the app has a leanback launch + * intent, the app link card will be {@code APP_LINK_TYPE_APP} which launches the application. + */ + int APP_LINK_TYPE_APP = 2; + /** Channel number delimiter between major and minor parts. */ + char CHANNEL_NUMBER_DELIMITER = '-'; + + long getId(); + + Uri getUri(); + + String getPackageName(); + + String getInputId(); + + String getType(); + + String getDisplayNumber(); + + @Nullable + String getDisplayName(); + + String getDescription(); + + String getVideoFormat(); + + boolean isPassthrough(); + + String getDisplayText(); + + String getAppLinkText(); + + int getAppLinkColor(); + + String getAppLinkIconUri(); + + String getAppLinkPosterArtUri(); + + String getAppLinkIntentUri(); + + String getLogoUri(); + + boolean isRecordingProhibited(); + + boolean isPhysicalTunerChannel(); + + boolean isBrowsable(); + + boolean isSearchable(); + + boolean isLocked(); + + boolean hasSameReadOnlyInfo(Channel mCurrentChannel); + + void setChannelLogoExist(boolean result); + + void setBrowsable(boolean browsable); + + void setLocked(boolean locked); + + void copyFrom(Channel channel); + + void setLogoUri(String logoUri); + + boolean channelLogoExists(); + + void loadBitmap( + Context context, + int loadImageTypeChannelLogo, + int mChannelLogoImageViewWidth, + int mChannelLogoImageViewHeight, + ImageLoaderCallback<?> channelLogoCallback); + + int getAppLinkType(Context context); + + Intent getAppLinkIntent(Context context); + + void prefetchImage( + Context mContext, + int loadImageTypeChannelLogo, + int mPosterArtWidth, + int mPosterArtHeight); +} diff --git a/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java b/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java index 90d109d7..795ad5c4 100644 --- a/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java +++ b/src/com/android/tv/data/epg/AutoValue_EpgReader_EpgChannel.java @@ -16,7 +16,7 @@ package com.android.tv.data.epg; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; /** * Hand copy of generated Autovalue class. diff --git a/src/com/android/tv/data/epg/EpgFetcherImpl.java b/src/com/android/tv/data/epg/EpgFetcherImpl.java index 3660486f..2aaaa5b2 100644 --- a/src/com/android/tv/data/epg/EpgFetcherImpl.java +++ b/src/com/android/tv/data/epg/EpgFetcherImpl.java @@ -49,11 +49,12 @@ import com.android.tv.common.util.LocationUtils; import com.android.tv.common.util.NetworkTrafficTags; import com.android.tv.common.util.PermissionUtils; import com.android.tv.common.util.PostalCodeUtils; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.ChannelImpl; import com.android.tv.data.ChannelLogoFetcher; import com.android.tv.data.Lineup; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.perf.EventNames; import com.android.tv.perf.PerformanceMonitor; import com.android.tv.perf.TimerEvent; @@ -180,13 +181,13 @@ public class EpgFetcherImpl implements EpgFetcher { context.getContentResolver() .query( TvContract.Channels.CONTENT_URI, - Channel.PROJECTION, + ChannelImpl.PROJECTION, selection, selectionArgs, null)) { if (c != null) { while (c.moveToNext()) { - Channel channel = Channel.fromCursor(c); + Channel channel = ChannelImpl.fromCursor(c); if (DEBUG) Log.d(TAG, "Found " + channel); if (myPackageName.equals(channel.getPackageName())) { channels.add(channel); @@ -543,12 +544,12 @@ public class EpgFetcherImpl implements EpgFetcher { mContext.getContentResolver() .query( TvContract.buildChannelsUriForInput(inputId), - Channel.PROJECTION, + ChannelImpl.PROJECTION, null, null, null)) { while (cursor.moveToNext()) { - result.add(Channel.fromCursor(cursor)); + result.add(ChannelImpl.fromCursor(cursor)); } return result; } diff --git a/src/com/android/tv/data/epg/EpgReader.java b/src/com/android/tv/data/epg/EpgReader.java index 3cc70aa1..7147905a 100644 --- a/src/com/android/tv/data/epg/EpgReader.java +++ b/src/com/android/tv/data/epg/EpgReader.java @@ -19,9 +19,9 @@ package com.android.tv.data.epg; import android.support.annotation.AnyThread; import android.support.annotation.NonNull; import android.support.annotation.WorkerThread; -import com.android.tv.data.Channel; import com.android.tv.data.Lineup; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.data.SeriesInfo; import java.util.Collection; import java.util.List; @@ -32,7 +32,7 @@ import java.util.Set; @WorkerThread public interface EpgReader { - /** Value class that holds a EpgChannelId and its corresponding Channel */ + /** Value class that holds a EpgChannelId and its corresponding {@link Channel} */ // TODO(b/72052568): Get autovalue to work in aosp master abstract class EpgChannel { public static EpgChannel createEpgChannel(Channel channel, String epgChannelId) { diff --git a/src/com/android/tv/data/epg/StubEpgReader.java b/src/com/android/tv/data/epg/StubEpgReader.java index 9a87619d..3b001481 100644 --- a/src/com/android/tv/data/epg/StubEpgReader.java +++ b/src/com/android/tv/data/epg/StubEpgReader.java @@ -18,9 +18,9 @@ package com.android.tv.data.epg; import android.content.Context; import android.support.annotation.NonNull; -import com.android.tv.data.Channel; import com.android.tv.data.Lineup; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.data.SeriesInfo; import java.util.Collection; import java.util.Collections; diff --git a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java index 173a2891..7e36591f 100644 --- a/src/com/android/tv/dialog/DvrHistoryDialogFragment.java +++ b/src/com/android/tv/dialog/DvrHistoryDialogFragment.java @@ -32,8 +32,8 @@ import android.widget.ListView; import android.widget.TextView; import com.android.tv.R; import com.android.tv.TvSingletons; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.ScheduledRecording.RecordingState; diff --git a/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java b/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java index 84fddbe3..eb6940fb 100644 --- a/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java +++ b/src/com/android/tv/dialog/RecentlyWatchedDialogFragment.java @@ -31,8 +31,8 @@ import android.widget.SimpleCursorAdapter; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; /** Displays the watch history */ public class RecentlyWatchedDialogFragment extends SafeDismissDialogFragment diff --git a/src/com/android/tv/dvr/BaseDvrDataManager.java b/src/com/android/tv/dvr/BaseDvrDataManager.java index 0befba9c..e890481b 100644 --- a/src/com/android/tv/dvr/BaseDvrDataManager.java +++ b/src/com/android/tv/dvr/BaseDvrDataManager.java @@ -245,6 +245,11 @@ public abstract class BaseDvrDataManager implements WritableDvrDataManager { } @Override + public List<ScheduledRecording> getFailedScheduledRecordings() { + return getRecordingsWithState(ScheduledRecording.STATE_RECORDING_FAILED); + } + + @Override public void changeState(ScheduledRecording scheduledRecording, @RecordingState int newState) { if (scheduledRecording.getState() != newState) { updateScheduledRecording( diff --git a/src/com/android/tv/dvr/DvrDataManager.java b/src/com/android/tv/dvr/DvrDataManager.java index 4ec0acfc..10dfc4c9 100644 --- a/src/com/android/tv/dvr/DvrDataManager.java +++ b/src/com/android/tv/dvr/DvrDataManager.java @@ -65,6 +65,9 @@ public interface DvrDataManager { /** Returns scheduled but not started recordings that have not expired. */ List<ScheduledRecording> getNonStartedScheduledRecordings(); + /** Returns failed recordings. */ + List<ScheduledRecording> getFailedScheduledRecordings(); + /** Returns series recordings. */ List<SeriesRecording> getSeriesRecordings(); diff --git a/src/com/android/tv/dvr/DvrDataManagerImpl.java b/src/com/android/tv/dvr/DvrDataManagerImpl.java index dbb5a9f6..c74aa208 100644 --- a/src/com/android/tv/dvr/DvrDataManagerImpl.java +++ b/src/com/android/tv/dvr/DvrDataManagerImpl.java @@ -228,7 +228,6 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { protected void onPostExecute(List<ScheduledRecording> result) { mPendingTasks.remove(this); long maxId = 0; - List<SeriesRecording> seriesRecordingsToAdd = new ArrayList<>(); List<ScheduledRecording> toUpdate = new ArrayList<>(); List<ScheduledRecording> toDelete = new ArrayList<>(); for (ScheduledRecording r : result) { @@ -273,6 +272,7 @@ public class DvrDataManagerImpl extends BaseDvrDataManager { case ScheduledRecording.STATE_RECORDING_CANCELED: toDelete.add(r); break; + default: // fall out } } if (maxId < r.getId()) { diff --git a/src/com/android/tv/dvr/DvrManager.java b/src/com/android/tv/dvr/DvrManager.java index 14a17f2f..63a245a3 100644 --- a/src/com/android/tv/dvr/DvrManager.java +++ b/src/com/android/tv/dvr/DvrManager.java @@ -40,8 +40,8 @@ import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.util.CommonUtils; -import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager.OnRecordedProgramLoadFinishedListener; import com.android.tv.dvr.DvrDataManager.RecordedProgramListener; import com.android.tv.dvr.DvrScheduleManager.OnInitializeListener; diff --git a/src/com/android/tv/dvr/DvrScheduleManager.java b/src/com/android/tv/dvr/DvrScheduleManager.java index cbb89290..d5126b12 100644 --- a/src/com/android/tv/dvr/DvrScheduleManager.java +++ b/src/com/android/tv/dvr/DvrScheduleManager.java @@ -27,9 +27,9 @@ import android.util.ArraySet; import android.util.Range; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.data.ScheduledRecording; @@ -509,13 +509,13 @@ public class DvrScheduleManager { public List<ScheduledRecording> getConflictingSchedules(Program program) { SoftPreconditions.checkState(mInitialized, TAG, "Not initialized yet"); SoftPreconditions.checkState( - Program.isValid(program), TAG, "Program is invalid: " + program); + Program.isProgramValid(program), TAG, "Program is invalid: " + program); SoftPreconditions.checkState( program.getStartTimeUtcMillis() < program.getEndTimeUtcMillis(), TAG, "Program duration is empty: " + program); if (!mInitialized - || !Program.isValid(program) + || !Program.isProgramValid(program) || program.getStartTimeUtcMillis() >= program.getEndTimeUtcMillis()) { return Collections.emptyList(); } diff --git a/src/com/android/tv/dvr/data/ScheduledRecording.java b/src/com/android/tv/dvr/data/ScheduledRecording.java index aa1dfc72..bc569d96 100644 --- a/src/com/android/tv/dvr/data/ScheduledRecording.java +++ b/src/com/android/tv/dvr/data/ScheduledRecording.java @@ -30,8 +30,8 @@ import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.util.CommonUtils; -import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.provider.DvrContract.Schedules; import com.android.tv.util.CompositeComparator; @@ -838,6 +838,11 @@ public final class ScheduledRecording implements Parcelable { return mState == STATE_RECORDING_IN_PROGRESS; } + /** Returns {@code true} if the recording is finished, otherwise @{code false}. */ + public boolean isFinished() { + return mState == STATE_RECORDING_FINISHED; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof ScheduledRecording)) { diff --git a/src/com/android/tv/dvr/recorder/ConflictChecker.java b/src/com/android/tv/dvr/recorder/ConflictChecker.java index f5bc7b9f..bfd315e9 100644 --- a/src/com/android/tv/dvr/recorder/ConflictChecker.java +++ b/src/com/android/tv/dvr/recorder/ConflictChecker.java @@ -32,8 +32,8 @@ import com.android.tv.InputSessionManager.OnTvViewChannelChangeListener; import com.android.tv.MainActivity; import com.android.tv.TvSingletons; import com.android.tv.common.WeakHandler; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.data.ScheduledRecording; diff --git a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java index 722e75fc..0f1ea3b0 100644 --- a/src/com/android/tv/dvr/recorder/InputTaskScheduler.java +++ b/src/com/android/tv/dvr/recorder/InputTaskScheduler.java @@ -27,8 +27,8 @@ import android.util.Log; import android.util.LongSparseArray; import com.android.tv.InputSessionManager; import com.android.tv.common.util.Clock; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.WritableDvrDataManager; diff --git a/src/com/android/tv/dvr/recorder/RecordingTask.java b/src/com/android/tv/dvr/recorder/RecordingTask.java index b337e244..ff37f3f0 100644 --- a/src/com/android/tv/dvr/recorder/RecordingTask.java +++ b/src/com/android/tv/dvr/recorder/RecordingTask.java @@ -37,7 +37,7 @@ import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.util.Clock; import com.android.tv.common.util.CommonUtils; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.WritableDvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; diff --git a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java index f30308f3..dd106e1c 100644 --- a/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java +++ b/src/com/android/tv/dvr/recorder/ScheduledProgramReaper.java @@ -17,7 +17,6 @@ package com.android.tv.dvr.recorder; import android.support.annotation.MainThread; -import android.support.annotation.VisibleForTesting; import com.android.tv.common.util.Clock; import com.android.tv.dvr.WritableDvrDataManager; import com.android.tv.dvr.data.ScheduledRecording; @@ -27,9 +26,9 @@ import java.util.List; import java.util.concurrent.TimeUnit; /** Deletes {@link ScheduledRecording} older than {@value @DAYS} days. */ -class ScheduledProgramReaper implements Runnable { +public class ScheduledProgramReaper implements Runnable { - @VisibleForTesting static final int DAYS = 2; + public static final int DAYS = 7; private final WritableDvrDataManager mDvrDataManager; private final Clock mClock; diff --git a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java index 24a6fcd3..6be35cb2 100644 --- a/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java +++ b/src/com/android/tv/dvr/ui/DvrChannelRecordDurationOptionFragment.java @@ -24,7 +24,7 @@ import android.support.v17.leanback.widget.GuidedAction; import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrConflictFragment.DvrChannelRecordConflictFragment; diff --git a/src/com/android/tv/dvr/ui/DvrConflictFragment.java b/src/com/android/tv/dvr/ui/DvrConflictFragment.java index 641f86c1..65759555 100644 --- a/src/com/android/tv/dvr/ui/DvrConflictFragment.java +++ b/src/com/android/tv/dvr/ui/DvrConflictFragment.java @@ -31,8 +31,8 @@ import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.recorder.ConflictChecker; import com.android.tv.dvr.recorder.ConflictChecker.OnUpcomingConflictChangeListener; diff --git a/src/com/android/tv/dvr/ui/DvrFutureProgramInfoFragment.java b/src/com/android/tv/dvr/ui/DvrFutureProgramInfoFragment.java index a51e583a..677a6cbb 100644 --- a/src/com/android/tv/dvr/ui/DvrFutureProgramInfoFragment.java +++ b/src/com/android/tv/dvr/ui/DvrFutureProgramInfoFragment.java @@ -37,7 +37,7 @@ public class DvrFutureProgramInfoFragment extends DvrGuidedStepFragment { @Override public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { long startTime = mProgram.getStartTimeUtcMillis(); - // TODO(b/71717923): use R.string whe the strings are finalized + // TODO(b/71717923): use R.string when the strings are finalized StringBuilder description = new StringBuilder() .append("This program will start at ") .append(Utils.getDurationString(getContext(), startTime, startTime, false)); diff --git a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java index 9383058a..37efa5ba 100644 --- a/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java +++ b/src/com/android/tv/dvr/ui/DvrSeriesSettingsFragment.java @@ -28,9 +28,10 @@ import android.support.v17.leanback.widget.GuidedActionsStylist; import android.util.LongSparseArray; import com.android.tv.R; import com.android.tv.TvSingletons; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.ChannelImpl; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; @@ -50,7 +51,6 @@ import java.util.Set; public class DvrSeriesSettingsFragment extends GuidedStepFragment implements DvrDataManager.SeriesRecordingListener { private static final String TAG = "SeriesSettingsFragment"; - private static final boolean DEBUG = false; private static final long ACTION_ID_PRIORITY = 10; private static final long ACTION_ID_CHANNEL = 11; @@ -127,7 +127,7 @@ public class DvrSeriesSettingsFragment extends GuidedStepFragment mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; } } - mChannels.sort(Channel.CHANNEL_NUMBER_COMPARATOR); + mChannels.sort(ChannelImpl.CHANNEL_NUMBER_COMPARATOR); mFragmentTitle = getString(R.string.dvr_series_settings_title); mProrityActionTitle = getString(R.string.dvr_series_settings_priority); mProrityActionHighestText = getString(R.string.dvr_series_settings_priority_highest); diff --git a/src/com/android/tv/dvr/ui/DvrUiHelper.java b/src/com/android/tv/dvr/ui/DvrUiHelper.java index 6977649f..e5786895 100644 --- a/src/com/android/tv/dvr/ui/DvrUiHelper.java +++ b/src/com/android/tv/dvr/ui/DvrUiHelper.java @@ -45,8 +45,8 @@ import com.android.tv.common.SoftPreconditions; import com.android.tv.common.recording.RecordingStorageStatusManager; import com.android.tv.common.util.CommonUtils; import com.android.tv.data.BaseProgram; -import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.RecordedProgram; @@ -67,6 +67,7 @@ import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrSmallSizedStorageErro import com.android.tv.dvr.ui.DvrHalfSizedDialogFragment.DvrStopRecordingDialogFragment; import com.android.tv.dvr.ui.browse.DvrBrowseActivity; import com.android.tv.dvr.ui.browse.DvrDetailsActivity; +import com.android.tv.dvr.ui.list.DvrHistoryActivity; import com.android.tv.dvr.ui.list.DvrSchedulesActivity; import com.android.tv.dvr.ui.list.DvrSchedulesFragment; import com.android.tv.dvr.ui.list.DvrSeriesSchedulesFragment; @@ -245,9 +246,7 @@ public class DvrUiHelper { return; } Bundle args = new Bundle(); - args.putParcelable( - DvrHalfSizedDialogFragment.KEY_PROGRAM, - program); + args.putParcelable(DvrHalfSizedDialogFragment.KEY_PROGRAM, program); showDialogFragment(activity, new DvrFutureProgramInfoDialogFragment(), args, false, true); } @@ -421,6 +420,12 @@ public class DvrUiHelper { } /** Shows the schedules activity with full schedule. */ + public static void startDvrHistoryActivity(Context context) { + Intent intent = new Intent(context, DvrHistoryActivity.class); + context.startActivity(intent); + } + + /** Shows the schedules activity with full schedule. */ public static void startSchedulesActivity( Context context, ScheduledRecording focusedScheduledRecording) { Intent intent = new Intent(context, DvrSchedulesActivity.class); diff --git a/src/com/android/tv/dvr/ui/browse/DetailsContent.java b/src/com/android/tv/dvr/ui/browse/DetailsContent.java index 70903373..56bbdb46 100644 --- a/src/com/android/tv/dvr/ui/browse/DetailsContent.java +++ b/src/com/android/tv/dvr/ui/browse/DetailsContent.java @@ -22,7 +22,7 @@ import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.tv.R; import com.android.tv.TvSingletons; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.data.SeriesRecording; diff --git a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java index 90326a8b..6d66eea1 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java +++ b/src/com/android/tv/dvr/ui/browse/DvrBrowseFragment.java @@ -32,6 +32,7 @@ import android.util.Log; import android.view.View; import android.view.ViewTreeObserver.OnGlobalFocusChangeListener; import com.android.tv.R; +import com.android.tv.TvFeatures; import com.android.tv.TvSingletons; import com.android.tv.data.GenreItems; import com.android.tv.dvr.DvrDataManager; @@ -185,6 +186,12 @@ public class DvrBrowseFragment extends BrowseFragment .addClassPresenter( FullScheduleCardHolder.class, new FullSchedulesCardPresenter(context)); + + if (TvFeatures.DVR_FAILED_LIST.isEnabled(context)) { + mPresenterSelector.addClassPresenter( + DvrHistoryCardHolder.class, + new DvrHistoryCardPresenter(context)); + } mGenreLabels = new ArrayList<>(Arrays.asList(GenreItems.getLabels(context))); mGenreLabels.add(getString(R.string.dvr_main_others)); prepareUiElements(); @@ -387,6 +394,9 @@ public class DvrBrowseFragment extends BrowseFragment for (RecordedProgram recordedProgram : mDvrDataManager.getRecordedPrograms()) { handleRecordedProgramAdded(recordedProgram, false); } + if (TvFeatures.DVR_FAILED_LIST.isEnabled(getContext())) { + mRecentAdapter.addExtraItem(DvrHistoryCardHolder.DVR_HISTORY_CARD_HOLDER); + } // Series Recordings. Series recordings should be added after recorded programs, because // we build series recordings' latest program information while adding recorded // programs. @@ -576,7 +586,8 @@ public class DvrBrowseFragment extends BrowseFragment private void updateRows() { int visibleRowsCount = 1; // Schedule's Row will never be empty - if (mRecentAdapter.isEmpty()) { + int recentRowMinSize = TvFeatures.DVR_FAILED_LIST.isEnabled(getContext()) ? 1 : 0; + if (mRecentAdapter.size() <= recentRowMinSize) { mRowsAdapter.remove(mRecentRow); } else { if (mRowsAdapter.indexOf(mRecentRow) < 0) { diff --git a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java index 209fc6e1..8f4e4dab 100644 --- a/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java +++ b/src/com/android/tv/dvr/ui/browse/DvrDetailsFragment.java @@ -40,15 +40,15 @@ import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.util.CommonUtils; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.dialog.PinDialogFragment; import com.android.tv.dialog.PinDialogFragment.OnPinCheckedListener; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.parental.ParentalControlSettings; -import com.android.tv.util.ImageLoader; import com.android.tv.util.ToastUtils; +import com.android.tv.util.images.ImageLoader; import java.io.File; abstract class DvrDetailsFragment extends DetailsFragment { diff --git a/src/com/android/tv/dvr/ui/browse/DvrHistoryCardHolder.java b/src/com/android/tv/dvr/ui/browse/DvrHistoryCardHolder.java new file mode 100644 index 00000000..c6288ef0 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DvrHistoryCardHolder.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +/** Special object for schedule preview; */ +final class DvrHistoryCardHolder { + /** Full schedule card holder. */ + static final DvrHistoryCardHolder DVR_HISTORY_CARD_HOLDER = new DvrHistoryCardHolder(); + + private DvrHistoryCardHolder() {} +} diff --git a/src/com/android/tv/dvr/ui/browse/DvrHistoryCardPresenter.java b/src/com/android/tv/dvr/ui/browse/DvrHistoryCardPresenter.java new file mode 100644 index 00000000..62c050c9 --- /dev/null +++ b/src/com/android/tv/dvr/ui/browse/DvrHistoryCardPresenter.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.dvr.ui.browse; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; +import com.android.tv.R; +import com.android.tv.dvr.ui.DvrUiHelper; + +/** Presents a DVR history card view in the {@link DvrBrowseFragment}. */ +class DvrHistoryCardPresenter extends DvrItemPresenter<Object> { + private final Drawable mIconDrawable; + private final String mCardTitleText; + + DvrHistoryCardPresenter(Context context) { + super(context); + mIconDrawable = mContext.getDrawable(R.drawable.dvr_full_schedule); + mCardTitleText = mContext.getString(R.string.dvr_history_card_view_title); + } + + @Override + public DvrItemViewHolder onCreateDvrItemViewHolder() { + return new DvrItemViewHolder(new RecordingCardView(mContext)); + } + + @Override + public void onBindDvrItemViewHolder(DvrItemViewHolder vh, Object o) { + final RecordingCardView cardView = (RecordingCardView) vh.view; + + cardView.setTitle(mCardTitleText); + cardView.setImage(mIconDrawable); + } + + @Override + public void onUnbindViewHolder(ViewHolder vh) { + ((RecordingCardView) vh.view).reset(); + super.onUnbindViewHolder(vh); + } + + @Override + protected View.OnClickListener onCreateOnClickListener() { + return new View.OnClickListener() { + @Override + public void onClick(View view) { + DvrUiHelper.startDvrHistoryActivity(mContext); + } + }; + } +} diff --git a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java index edee5d3a..fe3c52d9 100644 --- a/src/com/android/tv/dvr/ui/browse/RecordingCardView.java +++ b/src/com/android/tv/dvr/ui/browse/RecordingCardView.java @@ -34,7 +34,7 @@ import android.widget.TextView; import com.android.tv.R; import com.android.tv.dvr.data.RecordedProgram; import com.android.tv.ui.ViewUtils; -import com.android.tv.util.ImageLoader; +import com.android.tv.util.images.ImageLoader; /** * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording} or diff --git a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java index 84298bdf..77a63508 100644 --- a/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/BaseDvrSchedulesFragment.java @@ -109,7 +109,8 @@ public abstract class BaseDvrSchedulesFragment extends DetailsFragment public abstract ScheduleRowPresenter onCreateRowPresenter(); /** Creates rows adapter. */ - public abstract ScheduleRowAdapter onCreateRowsAdapter(ClassPresenterSelector presenterSelecor); + public abstract ScheduleRowAdapter onCreateRowsAdapter( + ClassPresenterSelector presenterSelector); /** Gets the first focus position in schedules list. */ protected int getFirstItemPosition() { diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryActivity.java b/src/com/android/tv/dvr/ui/list/DvrHistoryActivity.java new file mode 100644 index 00000000..623975e1 --- /dev/null +++ b/src/com/android/tv/dvr/ui/list/DvrHistoryActivity.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.list; + +import android.app.Activity; +import android.os.Bundle; +import com.android.tv.R; +import com.android.tv.Starter; + +/** Activity to show the recording history. */ +public class DvrHistoryActivity extends Activity { + + @Override + public void onCreate(final Bundle savedInstanceState) { + Starter.start(this); + // Pass null to prevent automatically re-creating fragments + super.onCreate(null); + setContentView(R.layout.activity_dvr_history); + DvrHistoryFragment dvrHistoryFragment = new DvrHistoryFragment(); + getFragmentManager() + .beginTransaction() + .add(R.id.fragment_container, dvrHistoryFragment) + .commit(); + } +} diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java b/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java new file mode 100644 index 00000000..fbf0fe53 --- /dev/null +++ b/src/com/android/tv/dvr/ui/list/DvrHistoryFragment.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.list; + +import android.os.Bundle; +import android.support.v17.leanback.app.DetailsFragment; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import com.android.tv.R; +import com.android.tv.TvSingletons; +import com.android.tv.dvr.ui.list.SchedulesHeaderRowPresenter.DateHeaderRowPresenter; + +/** A fragment to show the DVR history. */ +public class DvrHistoryFragment extends DetailsFragment { + + private DvrHistoryRowAdapter mRowsAdapter; + private TextView mEmptyInfoScreenView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ClassPresenterSelector presenterSelector = new ClassPresenterSelector(); + presenterSelector.addClassPresenter( + SchedulesHeaderRow.class, new DateHeaderRowPresenter(getContext())); + presenterSelector.addClassPresenter( + ScheduleRow.class, new ScheduleRowPresenter(getContext())); + mRowsAdapter = new DvrHistoryRowAdapter(getContext(), presenterSelector); + setAdapter(mRowsAdapter); + mRowsAdapter.start(); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); + mEmptyInfoScreenView = (TextView) getActivity().findViewById(R.id.empty_info_screen); + // TODO: handle show/hide message + } + + /** Shows the empty message. */ + void showEmptyMessage(int messageId) { + mEmptyInfoScreenView.setText(messageId); + if (mEmptyInfoScreenView.getVisibility() != View.VISIBLE) { + mEmptyInfoScreenView.setVisibility(View.VISIBLE); + } + } + + /** Hides the empty message. */ + void hideEmptyMessage() { + if (mEmptyInfoScreenView.getVisibility() == View.VISIBLE) { + mEmptyInfoScreenView.setVisibility(View.GONE); + } + } + + @Override + public View onInflateTitleView( + LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + // Workaround of b/31046014 + return null; + } +} diff --git a/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java b/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java new file mode 100644 index 00000000..7685b7e9 --- /dev/null +++ b/src/com/android/tv/dvr/ui/list/DvrHistoryRowAdapter.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.dvr.ui.list; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build.VERSION_CODES; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.text.format.DateUtils; +import com.android.tv.R; +import com.android.tv.TvSingletons; +import com.android.tv.dvr.data.RecordedProgram; +import com.android.tv.dvr.data.ScheduledRecording; +import com.android.tv.dvr.recorder.ScheduledProgramReaper; +import com.android.tv.dvr.ui.list.SchedulesHeaderRow.DateHeaderRow; +import com.android.tv.util.Utils; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** An adapter for DVR history. */ +@TargetApi(VERSION_CODES.N) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated +class DvrHistoryRowAdapter extends ArrayObjectAdapter { + // TODO: handle row added/removed/updated + + private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1); + private static final int MAX_HISTORY_DAYS = ScheduledProgramReaper.DAYS; + + private Context mContext; + private final List<String> mTitles = new ArrayList<>(); + + public DvrHistoryRowAdapter(Context context, ClassPresenterSelector classPresenterSelector) { + super(classPresenterSelector); + mContext = context; + mTitles.add(mContext.getString(R.string.dvr_date_today)); + mTitles.add(mContext.getString(R.string.dvr_date_yesterday)); + } + + /** Returns context. */ + protected Context getContext() { + return mContext; + } + + /** Starts row adapter. */ + public void start() { + clear(); + List<ScheduledRecording> recordingList = + TvSingletons.getSingletons(mContext) + .getDvrDataManager() + .getFailedScheduledRecordings(); + List<RecordedProgram> recordedProgramList = + TvSingletons.getSingletons(mContext) + .getDvrDataManager() + .getRecordedPrograms(); + + recordingList.addAll( + recordedProgramsToScheduledRecordings(recordedProgramList, MAX_HISTORY_DAYS)); + recordingList + .sort(ScheduledRecording.START_TIME_THEN_PRIORITY_THEN_ID_COMPARATOR.reversed()); + long deadLine = Utils.getFirstMillisecondOfDay(System.currentTimeMillis()); + for (int i = 0; i < recordingList.size(); ) { + ArrayList<ScheduledRecording> section = new ArrayList<>(); + while (i < recordingList.size() && recordingList.get(i).getStartTimeMs() >= deadLine) { + section.add(recordingList.get(i++)); + } + if (!section.isEmpty()) { + SchedulesHeaderRow headerRow = + new DateHeaderRow( + calculateHeaderDate(deadLine), + mContext.getResources() + .getQuantityString( + R.plurals.dvr_schedules_section_subtitle, + section.size(), + section.size()), + section.size(), + deadLine); + add(headerRow); + for (ScheduledRecording recording : section) { + add(new ScheduleRow(recording, headerRow)); + } + } + deadLine -= ONE_DAY_MS; + } + } + + private String calculateHeaderDate(long timeMs) { + int titleIndex = + (int) + ((Utils.getFirstMillisecondOfDay(System.currentTimeMillis()) - timeMs) + / ONE_DAY_MS); + String headerDate; + if (titleIndex < mTitles.size()) { + headerDate = mTitles.get(titleIndex); + } else { + headerDate = + DateUtils.formatDateTime( + getContext(), + timeMs, + DateUtils.FORMAT_SHOW_WEEKDAY + | DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_ABBREV_MONTH); + } + return headerDate; + } + + private List<ScheduledRecording> recordedProgramsToScheduledRecordings( + List<RecordedProgram> recordedPrograms, int maxDays) { + List<ScheduledRecording> result = new ArrayList<>(recordedPrograms.size()); + long firstMillisecondToday = Utils.getFirstMillisecondOfDay(System.currentTimeMillis()); + for (RecordedProgram recordedProgram : recordedPrograms) { + if (maxDays + < Utils.computeDateDifference( + recordedProgram.getStartTimeUtcMillis(), + firstMillisecondToday)) { + continue; + } + result.add(ScheduledRecording.builder(recordedProgram).build()); + } + return result; + } +} diff --git a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java index e5290601..d97b61f4 100644 --- a/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java +++ b/src/com/android/tv/dvr/ui/list/DvrSchedulesFragment.java @@ -43,8 +43,8 @@ public class DvrSchedulesFragment extends BaseDvrSchedulesFragment { } @Override - public ScheduleRowAdapter onCreateRowsAdapter(ClassPresenterSelector presenterSelecor) { - return new ScheduleRowAdapter(getContext(), presenterSelecor); + public ScheduleRowAdapter onCreateRowsAdapter(ClassPresenterSelector presenterSelector) { + return new ScheduleRowAdapter(getContext(), presenterSelector); } @Override diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRow.java b/src/com/android/tv/dvr/ui/list/ScheduleRow.java index f54c4203..ce5f9516 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRow.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRow.java @@ -110,6 +110,12 @@ class ScheduleRow { && mSchedule.getState() == ScheduledRecording.STATE_RECORDING_IN_PROGRESS; } + /** Checks if the schedule is failed. */ + public final boolean isRecordingFailed() { + return mSchedule != null + && mSchedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED; + } + /** Checks if the schedule has been canceled or not. */ public final boolean isScheduleCanceled() { return mSchedule != null diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java index 1215c19a..bbccdb15 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowAdapter.java @@ -16,7 +16,9 @@ package com.android.tv.dvr.ui.list; +import android.annotation.TargetApi; import android.content.Context; +import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -26,6 +28,7 @@ import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; import com.android.tv.R; +import com.android.tv.TvFeatures; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.dvr.DvrManager; @@ -39,6 +42,8 @@ import java.util.Set; import java.util.concurrent.TimeUnit; /** An adapter for {@link ScheduleRow}. */ +@TargetApi(VERSION_CODES.N) +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated class ScheduleRowAdapter extends ArrayObjectAdapter { private static final String TAG = "ScheduleRowAdapter"; private static final boolean DEBUG = false; @@ -110,6 +115,33 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { } deadLine += ONE_DAY_MS; } + if (TvFeatures.DVR_FAILED_LIST.isEnabled(getContext())) { + List<ScheduledRecording> failedRecordingList = + TvSingletons.getSingletons(mContext) + .getDvrDataManager() + .getFailedScheduledRecordings(); + Collections.sort( + failedRecordingList, + ScheduledRecording.START_TIME_COMPARATOR.reversed()); + if (!failedRecordingList.isEmpty()) { + SchedulesHeaderRow headerRow = + // TODO(b/72638385): use R.string + // TODO(b/72638385): define another HeaderRow class + new DateHeaderRow( + "Failed recordings", + mContext.getResources() + .getQuantityString( + R.plurals.dvr_schedules_section_subtitle, + failedRecordingList.size(), + failedRecordingList.size()), + failedRecordingList.size(), + Long.MAX_VALUE); + add(headerRow); + for (ScheduledRecording recording : failedRecordingList) { + add(new ScheduleRow(recording, headerRow)); + } + } + } sendNextUpdateMessage(System.currentTimeMillis()); } @@ -382,7 +414,9 @@ class ScheduleRowAdapter extends ArrayObjectAdapter { Object item = get(i); if (item instanceof ScheduleRow) { ScheduleRow row = (ScheduleRow) item; - if (row.getEndTimeMs() <= currentTimeMs) { + ScheduledRecording recording = row.getSchedule(); + if (row.getEndTimeMs() <= currentTimeMs && (recording == null + || recording.getState() != ScheduledRecording.STATE_RECORDING_FAILED)) { removeScheduleRow(row); } } diff --git a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java index 5cab607a..48430956 100644 --- a/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java +++ b/src/com/android/tv/dvr/ui/list/ScheduleRowPresenter.java @@ -18,7 +18,6 @@ package com.android.tv.dvr.ui.list; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; @@ -38,9 +37,10 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.android.tv.R; +import com.android.tv.TvFeatures; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.DvrScheduleManager; @@ -268,19 +268,11 @@ class ScheduleRowPresenter extends RowPresenter { .translationX(targetTranslationX) .alpha(1f) .setUpdateListener( - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - // Set width to the proper width for this animation step. - lp.width = - targetWidth - + Math.round( - deltaWidth - * (1f - - animation - .getAnimatedFraction())); - mSelectorView.requestLayout(); - } + animation -> { + // Set width to the proper width for this animation step. + float fraction = 1f - animation.getAnimatedFraction(); + lp.width = targetWidth + Math.round(deltaWidth * fraction); + mSelectorView.requestLayout(); }) .setDuration(animationDuration) .setInterpolator(interpolator) @@ -430,11 +422,22 @@ class ScheduleRowPresenter extends RowPresenter { case 1: viewHolder.mFirstActionView.setImageResource(getImageForAction(actions[0])); break; + default: // fall out } } - if (mDvrManager.isConflicting(row.getSchedule())) { + ScheduledRecording schedule = row.getSchedule(); + if (mDvrManager.isConflicting(schedule) + || (TvFeatures.DVR_FAILED_LIST.isEnabled(getContext()) + && schedule != null + && schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED)) { String conflictInfo; - if (mDvrScheduleManager.isPartiallyConflicting(row.getSchedule())) { + if (TvFeatures.DVR_FAILED_LIST.isEnabled(getContext()) + && schedule != null + && schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED) { + // TODO(b/72638385): show real error messages + // TODO(b/72638385): use a better name for ConflictInfoXXX + conflictInfo = "Failed"; + } else if (mDvrScheduleManager.isPartiallyConflicting(row.getSchedule())) { conflictInfo = mTunerConflictWillBePartiallyRecordedInfo; } else { conflictInfo = mTunerConflictWillNotBeRecordedInfo; @@ -475,8 +478,13 @@ class ScheduleRowPresenter extends RowPresenter { /** Returns time text for time view from scheduled recording. */ protected String onGetRecordingTimeText(ScheduleRow row) { + boolean showDate = + TvFeatures.DVR_FAILED_LIST.isEnabled(getContext()) + && row.getSchedule() != null + && row.getSchedule().getState() + == ScheduledRecording.STATE_RECORDING_FAILED; return Utils.getDurationString( - mContext, row.getStartTimeMs(), row.getEndTimeMs(), true, false, true, 0); + mContext, row.getStartTimeMs(), row.getEndTimeMs(), true, showDate, true, 0); } /** Returns program info text for program title view. */ @@ -502,8 +510,10 @@ class ScheduleRowPresenter extends RowPresenter { } private boolean isInfoClickable(ScheduleRow row) { - return row.getSchedule() != null - && (row.getSchedule().isNotStarted() || row.getSchedule().isInProgress()); + ScheduledRecording schedule = row.getSchedule(); + // TODO: handle onClicked for finished schedules + return schedule != null + && (schedule.isNotStarted() || schedule.isInProgress() || schedule.isFinished()); } /** Called when the button in a row is clicked. */ @@ -521,6 +531,7 @@ class ScheduleRowPresenter extends RowPresenter { case ACTION_REMOVE_SCHEDULE: onRemoveSchedule(row); break; + default: // fall out } } @@ -653,6 +664,9 @@ class ScheduleRowPresenter extends RowPresenter { ScheduledRecording.buildFrom(row.getSchedule()) .setState(ScheduledRecording.STATE_RECORDING_CANCELED) .build()); + } else if (row.isRecordingFailed()) { + deletedInfo = getDeletedInfo(row); + mDvrManager.removeScheduledRecording(row.getSchedule()); } } if (deletedInfo != null) { @@ -821,6 +835,10 @@ class ScheduleRowPresenter extends RowPresenter { return new int[] {ACTION_REMOVE_SCHEDULE, ACTION_CREATE_SCHEDULE}; } else if (row.isRecordingNotStarted()) { return new int[] {ACTION_REMOVE_SCHEDULE}; + } else if (row.isRecordingFailed()) { + return new int[] {ACTION_REMOVE_SCHEDULE}; + } else if (row.isRecordingFinished()) { + return new int[] {}; } else { SoftPreconditions.checkState( false, diff --git a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java index dd17b22d..bef036eb 100644 --- a/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java +++ b/src/com/android/tv/dvr/ui/playback/DvrPlaybackMediaSessionHelper.java @@ -30,13 +30,13 @@ import android.support.annotation.Nullable; import android.text.TextUtils; import com.android.tv.R; import com.android.tv.TvSingletons; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrWatchedPositionManager; import com.android.tv.dvr.data.RecordedProgram; -import com.android.tv.util.ImageLoader; import com.android.tv.util.TimeShiftUtils; import com.android.tv.util.Utils; +import com.android.tv.util.images.ImageLoader; class DvrPlaybackMediaSessionHelper { private static final String TAG = "DvrPlaybackMediaSessionHelper"; diff --git a/src/com/android/tv/guide/ProgramItemView.java b/src/com/android/tv/guide/ProgramItemView.java index 9daa9f2f..9f379e43 100644 --- a/src/com/android/tv/guide/ProgramItemView.java +++ b/src/com/android/tv/guide/ProgramItemView.java @@ -24,7 +24,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.StateListDrawable; import android.os.Handler; -import android.os.SystemClock; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; @@ -40,7 +39,10 @@ import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.data.Channel; +import com.android.tv.common.util.Clock; +import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrUiHelper; @@ -70,8 +72,10 @@ public class ProgramItemView extends TextView { private static TextAppearanceSpan sEpisodeTitleStyle; private static TextAppearanceSpan sGrayedOutEpisodeTitleStyle; + private final DvrManager mDvrManager; + private final Clock mClock; + private final ChannelDataManager mChannelDataManager; private ProgramGuide mProgramGuide; - private DvrManager mDvrManager; private TableEntry mTableEntry; private int mMaxWidthForRipple; private int mTextWidth; @@ -86,6 +90,7 @@ public class ProgramItemView extends TextView { @Override public void onClick(final View view) { TableEntry entry = ((ProgramItemView) view).mTableEntry; + Clock clock = ((ProgramItemView) view).mClock; if (entry == null) { // do nothing return; @@ -114,7 +119,7 @@ public class ProgramItemView extends TextView { } else if (entry.program != null && CommonFeatures.DVR.isEnabled(view.getContext())) { DvrManager dvrManager = singletons.getDvrManager(); - if (entry.entryStartUtcMillis > System.currentTimeMillis() + if (entry.entryStartUtcMillis > clock.currentTimeMillis() && dvrManager.isProgramRecordable(entry.program)) { if (entry.scheduledRecording == null) { DvrUiHelper.checkStorageStatusAndShowErrorMessage( @@ -180,7 +185,8 @@ public class ProgramItemView extends TextView { background.jumpToCurrentState(); } int progress = - getProgress(entry.entryStartUtcMillis, entry.entryEndUtcMillis); + getProgress( + mClock, entry.entryStartUtcMillis, entry.entryEndUtcMillis); setProgress(background, R.id.reverse_progress, MAX_PROGRESS - progress); } if (getHandler() != null) { @@ -188,8 +194,7 @@ public class ProgramItemView extends TextView { .postAtTime( this, Utils.ceilTime( - SystemClock.uptimeMillis(), - FOCUS_UPDATE_FREQUENCY)); + mClock.uptimeMillis(), FOCUS_UPDATE_FREQUENCY)); } } }; @@ -206,7 +211,10 @@ public class ProgramItemView extends TextView { super(context, attrs, defStyle); setOnClickListener(ON_CLICKED); setOnFocusChangeListener(ON_FOCUS_CHANGED); - mDvrManager = TvSingletons.getSingletons(getContext()).getDvrManager(); + TvSingletons singletons = TvSingletons.getSingletons(getContext()); + mDvrManager = singletons.getDvrManager(); + mChannelDataManager = singletons.getChannelDataManager(); + mClock = singletons.getClock(); } private void initIfNeeded() { @@ -266,7 +274,7 @@ public class ProgramItemView extends TextView { @Override protected int[] onCreateDrawableState(int extraSpace) { if (mTableEntry != null) { - int states[] = + int[] states = super.onCreateDrawableState( extraSpace + STATE_CURRENT_PROGRAM.length + STATE_TOO_WIDE.length); if (mTableEntry.isCurrentProgram()) { @@ -296,78 +304,154 @@ public class ProgramItemView extends TextView { mTableEntry = entry; ViewGroup.LayoutParams layoutParams = getLayoutParams(); - layoutParams.width = entry.getWidth(); - setLayoutParams(layoutParams); + if (layoutParams != null) { + // There is no layoutParams in the tests so we skip this + layoutParams.width = entry.getWidth(); + setLayoutParams(layoutParams); + } + String title = mTableEntry.program != null ? mTableEntry.program.getTitle() : null; + if (mTableEntry.isGap()) { + title = gapTitle; + } + if (TextUtils.isEmpty(title)) { + title = getResources().getString(R.string.program_title_for_no_information); + } + updateText(selectedGenreId, title); + updateIcons(); + updateContentDescription(title); + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + mTextWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); + // Maximum width for us to use a ripple + mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis); + } + + private boolean isEntryWideEnough() { + return mTableEntry != null && mTableEntry.getWidth() >= sVisibleThreshold; + } + + private void updateText(int selectedGenreId, String title) { + if (!isEntryWideEnough()) { + setText(null); + return; + } - String title = entry.program != null ? entry.program.getTitle() : null; String episode = - entry.program != null ? entry.program.getEpisodeDisplayTitle(getContext()) : null; + mTableEntry.program != null + ? mTableEntry.program.getEpisodeDisplayTitle(getContext()) + : null; TextAppearanceSpan titleStyle = sGrayedOutProgramTitleStyle; TextAppearanceSpan episodeStyle = sGrayedOutEpisodeTitleStyle; + if (mTableEntry.isGap()) { - if (entry.getWidth() < sVisibleThreshold) { - setText(null); + episode = null; + } else if (mTableEntry.hasGenre(selectedGenreId)) { + titleStyle = sProgramTitleStyle; + episodeStyle = sEpisodeTitleStyle; + } + SpannableStringBuilder description = new SpannableStringBuilder(); + description.append(title); + if (!TextUtils.isEmpty(episode)) { + description.append('\n'); + + // Add a 'zero-width joiner'/ZWJ in order to ensure we have the same line height for + // all lines. This is a non-printing character so it will not change the horizontal + // spacing however it will affect the line height. As we ensure the ZWJ has the same + // text style as the title it will make sure the line height is consistent. + description.append('\u200D'); + + int middle = description.length(); + description.append(episode); + + description.setSpan(titleStyle, 0, middle, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + description.setSpan( + episodeStyle, middle, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - if (entry.isGap()) { - title = gapTitle; - episode = null; - } else if (entry.hasGenre(selectedGenreId)) { - titleStyle = sProgramTitleStyle; - episodeStyle = sEpisodeTitleStyle; + description.setSpan( + titleStyle, 0, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + setText(description); + } + + private void updateIcons() { + // Sets recording icons if needed. + int iconResId = 0; + if (isEntryWideEnough() && mTableEntry.scheduledRecording != null) { + if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) { + iconResId = R.drawable.ic_warning_white_18dp; + } else { + switch (mTableEntry.scheduledRecording.getState()) { + case ScheduledRecording.STATE_RECORDING_NOT_STARTED: + iconResId = R.drawable.ic_scheduled_recording; + break; + case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: + iconResId = R.drawable.ic_recording_program; + break; + default: + // leave the iconResId=0 + } } - if (TextUtils.isEmpty(title)) { - title = getResources().getString(R.string.program_title_for_no_information); + } + setCompoundDrawablePadding(iconResId != 0 ? sCompoundDrawablePadding : 0); + setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconResId, 0); + } + + private void updateContentDescription(String title) { + // The content description includes extra information that is displayed on the detail view + Resources resources = getResources(); + String description = title; + // TODO(b/73282818): only say channel name when the row changes + Channel channel = mChannelDataManager.getChannel(mTableEntry.channelId); + if (channel != null) { + description = channel.getDisplayNumber() + " " + description; + } + description += + " " + + Utils.getDurationString( + getContext(), + mClock, + mTableEntry.entryStartUtcMillis, + mTableEntry.entryEndUtcMillis, + true); + Program program = mTableEntry.program; + if (program != null) { + String episodeDescription = program.getEpisodeContentDescription(getContext()); + if (!TextUtils.isEmpty(episodeDescription)) { + description += " " + episodeDescription; } - SpannableStringBuilder description = new SpannableStringBuilder(); - description.append(title); - if (!TextUtils.isEmpty(episode)) { - description.append('\n'); - - // Add a 'zero-width joiner'/ZWJ in order to ensure we have the same line height for - // all lines. This is a non-printing character so it will not change the horizontal - // spacing however it will affect the line height. As we ensure the ZWJ has the same - // text style as the title it will make sure the line height is consistent. - description.append('\u200D'); - - int middle = description.length(); - description.append(episode); - - description.setSpan(titleStyle, 0, middle, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - description.setSpan( - episodeStyle, - middle, - description.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (mTableEntry.scheduledRecording != null) { + if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) { + description += + " " + resources.getString(R.string.dvr_epg_program_recording_conflict); } else { - description.setSpan( - titleStyle, 0, description.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - setText(description); - - // Sets recording icons if needed. - int iconResId = 0; - if (mTableEntry.scheduledRecording != null) { - if (mDvrManager.isConflicting(mTableEntry.scheduledRecording)) { - iconResId = R.drawable.ic_warning_white_18dp; - } else { - switch (mTableEntry.scheduledRecording.getState()) { - case ScheduledRecording.STATE_RECORDING_NOT_STARTED: - iconResId = R.drawable.ic_scheduled_recording; - break; - case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: - iconResId = R.drawable.ic_recording_program; - break; - } + switch (mTableEntry.scheduledRecording.getState()) { + case ScheduledRecording.STATE_RECORDING_NOT_STARTED: + description += + " " + + resources.getString( + R.string.dvr_epg_program_recording_scheduled); + break; + case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: + description += + " " + + resources.getString( + R.string.dvr_epg_program_recording_in_progress); + break; + default: + // do nothing } } - setCompoundDrawablePadding(iconResId != 0 ? sCompoundDrawablePadding : 0); - setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, iconResId, 0); } - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - mTextWidth = getMeasuredWidth() - getPaddingStart() - getPaddingEnd(); - // Maximum width for us to use a ripple - mMaxWidthForRipple = GuideUtils.convertMillisToPixel(fromUtcMillis, toUtcMillis); + if (mTableEntry.isBlocked()) { + description += " " + resources.getString(R.string.program_guide_content_locked); + } else if (program != null) { + String programDescription = program.getDescription(); + if (!TextUtils.isEmpty(programDescription)) { + description += " " + programDescription; + } + } + setContentDescription(description); } /** Update programItemView to handle alignments of text. */ @@ -423,8 +507,8 @@ public class ProgramItemView extends TextView { mTableEntry = null; } - private static int getProgress(long start, long end) { - long currentTime = System.currentTimeMillis(); + private static int getProgress(Clock clock, long start, long end) { + long currentTime = clock.currentTimeMillis(); if (currentTime <= start) { return 0; } else if (currentTime >= end) { diff --git a/src/com/android/tv/guide/ProgramListAdapter.java b/src/com/android/tv/guide/ProgramListAdapter.java index d997db1e..397bacfb 100644 --- a/src/com/android/tv/guide/ProgramListAdapter.java +++ b/src/com/android/tv/guide/ProgramListAdapter.java @@ -23,7 +23,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.android.tv.R; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener; import com.android.tv.guide.ProgramManager.TableEntry; diff --git a/src/com/android/tv/guide/ProgramManager.java b/src/com/android/tv/guide/ProgramManager.java index 6d355c7a..3f20a837 100644 --- a/src/com/android/tv/guide/ProgramManager.java +++ b/src/com/android/tv/guide/ProgramManager.java @@ -18,13 +18,14 @@ package com.android.tv.guide; import android.support.annotation.MainThread; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import android.util.ArraySet; import android.util.Log; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.GenreItems; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrScheduleManager; import com.android.tv.dvr.DvrScheduleManager.OnConflictStateChangeListener; @@ -731,7 +732,7 @@ public class ProgramManager { /** Returns true if this is a gap. */ boolean isGap() { - return !Program.isValid(program); + return !Program.isProgramValid(program); } /** Returns true if this channel is blocked. */ @@ -772,6 +773,23 @@ public class ProgramManager { } } + @VisibleForTesting + public static TableEntry createTableEntryForTest( + long channelId, + Program program, + ScheduledRecording scheduledRecording, + long entryStartUtcMillis, + long entryEndUtcMillis, + boolean isBlocked) { + return new TableEntry( + channelId, + program, + scheduledRecording, + entryStartUtcMillis, + entryEndUtcMillis, + isBlocked); + } + interface Listener { void onGenresUpdated(); diff --git a/src/com/android/tv/guide/ProgramRow.java b/src/com/android/tv/guide/ProgramRow.java index 3765aa43..83175bb6 100644 --- a/src/com/android/tv/guide/ProgramRow.java +++ b/src/com/android/tv/guide/ProgramRow.java @@ -24,7 +24,7 @@ import android.util.Log; import android.util.Range; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.guide.ProgramManager.TableEntry; import com.android.tv.util.Utils; import java.util.concurrent.TimeUnit; diff --git a/src/com/android/tv/guide/ProgramTableAdapter.java b/src/com/android/tv/guide/ProgramTableAdapter.java index 935f8b3a..6e7485ac 100644 --- a/src/com/android/tv/guide/ProgramTableAdapter.java +++ b/src/com/android/tv/guide/ProgramTableAdapter.java @@ -51,21 +51,21 @@ import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.util.CommonUtils; -import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.Program.CriticScore; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter; -import com.android.tv.util.ImageCache; -import com.android.tv.util.ImageLoader; -import com.android.tv.util.ImageLoader.ImageLoaderCallback; -import com.android.tv.util.ImageLoader.LoadTvInputLogoTask; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; +import com.android.tv.util.images.ImageCache; +import com.android.tv.util.images.ImageLoader; +import com.android.tv.util.images.ImageLoader.ImageLoaderCallback; +import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask; import java.util.ArrayList; import java.util.List; @@ -316,7 +316,6 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr @Override public void onAccessibilityStateChanged(boolean enable) { enable &= !CommonUtils.isRunningInTest(); - mDetailView.setFocusable(enable); mChannelHeaderView.setFocusable(enable); } }; @@ -370,7 +369,6 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr boolean accessibilityEnabled = mAccessibilityManager.isEnabled() && !CommonUtils.isRunningInTest(); - mDetailView.setFocusable(accessibilityEnabled); mChannelHeaderView.setFocusable(accessibilityEnabled); } @@ -471,7 +469,7 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr return; } - if (Program.isValid(mSelectedEntry.program)) { + if (Program.isProgramValid(mSelectedEntry.program)) { Program program = mSelectedEntry.program; if (getProgramBlock(program) == null) { program.prefetchPosterArt(itemView.getContext(), mImageWidth, mImageHeight); @@ -543,7 +541,7 @@ class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.Progr } if (DEBUG) Log.d(TAG, "updateDetailView"); mCriticScoresLayout.removeAllViews(); - if (Program.isValid(mSelectedEntry.program)) { + if (Program.isProgramValid(mSelectedEntry.program)) { mTitleView.setTextColor(mDetailTextColor); Context context = itemView.getContext(); Program program = mSelectedEntry.program; diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java index 3e60723f..fd93c314 100644 --- a/src/com/android/tv/menu/AppLinkCardView.java +++ b/src/com/android/tv/menu/AppLinkCardView.java @@ -35,10 +35,10 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.data.Channel; -import com.android.tv.util.BitmapUtils; -import com.android.tv.util.ImageLoader; +import com.android.tv.data.api.Channel; import com.android.tv.util.TvInputManagerHelper; +import com.android.tv.util.images.BitmapUtils; +import com.android.tv.util.images.ImageLoader; import java.util.Objects; /** A view to render an app link card. */ @@ -141,7 +141,7 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> { protected void onPostExecute(CharSequence appLabel) { mTvInputManagerHelper.setTvInputApplicationLabel( mLoadTvInputId, appLabel); - if (mLoadTvInputId != mChannel.getInputId() + if (mLoadTvInputId.equals(mChannel.getInputId()) || !isAttachedToWindow()) { return; } @@ -339,7 +339,7 @@ public class AppLinkCardView extends BaseCardView<ChannelsRowItem> { @Override protected void onPostExecute(Drawable banner) { - if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) { + if (mLoadTvInputId.equals(mChannel.getInputId()) || !isAttachedToWindow()) { return; } if (banner != null) { diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java index 31262b88..76056ee4 100644 --- a/src/com/android/tv/menu/ChannelCardView.java +++ b/src/com/android/tv/menu/ChannelCardView.java @@ -28,10 +28,10 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.parental.ParentalControlSettings; -import com.android.tv.util.ImageLoader; +import com.android.tv.util.images.ImageLoader; import java.util.Objects; /** A view to render channel card. */ diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java index 854bb5a8..9cecb9c0 100644 --- a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java +++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java @@ -26,9 +26,10 @@ import android.util.Log; import com.android.tv.R; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.WeakHandler; -import com.android.tv.data.Channel; +import com.android.tv.data.ChannelImpl; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.api.Channel; import java.util.List; /** A poster image prefetcher to show the program poster art in the Channels row faster. */ @@ -94,7 +95,7 @@ public class ChannelsPosterPrefetcher { return; } Channel channel = item.getChannel(); - if (!Channel.isValid(channel)) { + if (!ChannelImpl.isValid(channel)) { continue; } channel.prefetchImage( diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java index 2d86ec75..8536ef1f 100644 --- a/src/com/android/tv/menu/ChannelsRowAdapter.java +++ b/src/com/android/tv/menu/ChannelsRowAdapter.java @@ -24,7 +24,8 @@ import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.data.Channel; +import com.android.tv.data.ChannelImpl; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrDataManager; import com.android.tv.recommendation.Recommender; import com.android.tv.util.TvInputManagerHelper; @@ -168,7 +169,7 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels } if (needToShowAppLinkItem()) { ChannelsRowItem.APP_LINK_ITEM.setChannel( - new Channel.Builder(getMainActivity().getCurrentChannel()).build()); + new ChannelImpl.Builder(getMainActivity().getCurrentChannel()).build()); items.add(ChannelsRowItem.APP_LINK_ITEM); } for (Channel channel : getRecentChannels()) { @@ -193,7 +194,7 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channels .getCurrentChannel() .hasSameReadOnlyInfo(ChannelsRowItem.APP_LINK_ITEM.getChannel())) { ChannelsRowItem.APP_LINK_ITEM.setChannel( - new Channel.Builder(getMainActivity().getCurrentChannel()).build()); + new ChannelImpl.Builder(getMainActivity().getCurrentChannel()).build()); notifyItemChanged(currentIndex); } ++currentIndex; diff --git a/src/com/android/tv/menu/ChannelsRowItem.java b/src/com/android/tv/menu/ChannelsRowItem.java index 25216920..608bb36e 100644 --- a/src/com/android/tv/menu/ChannelsRowItem.java +++ b/src/com/android/tv/menu/ChannelsRowItem.java @@ -18,7 +18,7 @@ package com.android.tv.menu; import android.support.annotation.NonNull; import com.android.tv.R; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; /** A class for the items in channels row. */ public class ChannelsRowItem { diff --git a/src/com/android/tv/menu/MenuUpdater.java b/src/com/android/tv/menu/MenuUpdater.java index 2d81b93a..5d277824 100644 --- a/src/com/android/tv/menu/MenuUpdater.java +++ b/src/com/android/tv/menu/MenuUpdater.java @@ -21,7 +21,7 @@ import com.android.tv.ChannelTuner; import com.android.tv.TvOptionsManager; import com.android.tv.TvOptionsManager.OptionChangedListener; import com.android.tv.TvOptionsManager.OptionType; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.menu.MenuRowFactory.TvOptionsRow; import com.android.tv.ui.TunableTvView; import com.android.tv.ui.TunableTvView.OnScreenBlockingChangedListener; diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java index 35f1d503..496d1969 100644 --- a/src/com/android/tv/menu/PlayControlsRowView.java +++ b/src/com/android/tv/menu/PlayControlsRowView.java @@ -31,8 +31,8 @@ import com.android.tv.TimeShiftManager.TimeShiftActionId; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; diff --git a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java index 5da802c4..410b8252 100644 --- a/src/com/android/tv/recommendation/ChannelPreviewUpdater.java +++ b/src/com/android/tv/recommendation/ChannelPreviewUpdater.java @@ -30,10 +30,10 @@ import android.text.TextUtils; import android.util.Log; import com.android.tv.Starter; import com.android.tv.TvSingletons; -import com.android.tv.data.Channel; import com.android.tv.data.PreviewDataManager; import com.android.tv.data.PreviewProgramContent; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.util.Utils; import java.util.ArrayList; diff --git a/src/com/android/tv/recommendation/ChannelRecord.java b/src/com/android/tv/recommendation/ChannelRecord.java index 34679452..c7a7cb37 100644 --- a/src/com/android/tv/recommendation/ChannelRecord.java +++ b/src/com/android/tv/recommendation/ChannelRecord.java @@ -20,9 +20,9 @@ import android.content.Context; import android.support.annotation.GuardedBy; import android.support.annotation.VisibleForTesting; import com.android.tv.TvSingletons; -import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.api.Channel; import java.util.ArrayDeque; import java.util.Deque; diff --git a/src/com/android/tv/recommendation/NotificationService.java b/src/com/android/tv/recommendation/NotificationService.java index 65da651f..f40a0862 100644 --- a/src/com/android/tv/recommendation/NotificationService.java +++ b/src/com/android/tv/recommendation/NotificationService.java @@ -46,13 +46,13 @@ import com.android.tv.Starter; import com.android.tv.TvSingletons; import com.android.tv.common.CommonConstants; import com.android.tv.common.WeakHandler; -import com.android.tv.data.Channel; import com.android.tv.data.Program; -import com.android.tv.util.BitmapUtils; -import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; -import com.android.tv.util.ImageLoader; +import com.android.tv.data.api.Channel; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; +import com.android.tv.util.images.BitmapUtils; +import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo; +import com.android.tv.util.images.ImageLoader; import java.util.ArrayList; import java.util.List; @@ -409,8 +409,7 @@ public class NotificationService extends Service Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, mChannelLogoMaxWidth, mChannelLogoMaxHeight, - createChannelLogoCallback( - this, notificationId, channel, program, posterArtBitmap)); + createChannelLogoCallback(this, notificationId, channel, program, posterArtBitmap)); if (mNotificationChannels[notificationId] == Channel.INVALID_ID) { ++mCurrentNotificationCount; @@ -481,11 +480,7 @@ public class NotificationService extends Service @Override public void onBitmapLoaded(NotificationService service, Bitmap channelLogo) { service.sendNotification( - notificationId, - channelLogo, - channel, - posterArtBitmap, - program); + notificationId, channelLogo, channel, posterArtBitmap, program); } }; } diff --git a/src/com/android/tv/recommendation/RecommendationDataManager.java b/src/com/android/tv/recommendation/RecommendationDataManager.java index 08825a9a..649920fb 100644 --- a/src/com/android/tv/recommendation/RecommendationDataManager.java +++ b/src/com/android/tv/recommendation/RecommendationDataManager.java @@ -36,10 +36,10 @@ import android.support.annotation.WorkerThread; import com.android.tv.TvSingletons; import com.android.tv.common.WeakHandler; import com.android.tv.common.util.PermissionUtils; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.data.WatchedHistoryManager; +import com.android.tv.data.api.Channel; import com.android.tv.util.TvUriMatcher; import java.util.ArrayList; import java.util.Collection; diff --git a/src/com/android/tv/recommendation/Recommender.java b/src/com/android/tv/recommendation/Recommender.java index 7542286f..f350799f 100644 --- a/src/com/android/tv/recommendation/Recommender.java +++ b/src/com/android/tv/recommendation/Recommender.java @@ -20,7 +20,7 @@ import android.content.Context; import android.support.annotation.VisibleForTesting; import android.util.Log; import android.util.Pair; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; diff --git a/src/com/android/tv/search/DataManagerSearch.java b/src/com/android/tv/search/DataManagerSearch.java index e17c4c86..82fb5016 100644 --- a/src/com/android/tv/search/DataManagerSearch.java +++ b/src/com/android/tv/search/DataManagerSearch.java @@ -27,10 +27,10 @@ import android.support.annotation.MainThread; import android.text.TextUtils; import android.util.Log; import com.android.tv.TvSingletons; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.search.LocalSearchProvider.SearchResult; import com.android.tv.util.MainThreadExecutor; import com.android.tv.util.Utils; diff --git a/src/com/android/tv/search/ProgramGuideSearchFragment.java b/src/com/android/tv/search/ProgramGuideSearchFragment.java index 3d8050c6..cb26252b 100644 --- a/src/com/android/tv/search/ProgramGuideSearchFragment.java +++ b/src/com/android/tv/search/ProgramGuideSearchFragment.java @@ -41,7 +41,7 @@ import android.view.ViewGroup; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.common.util.PermissionUtils; -import com.android.tv.util.ImageLoader; +import com.android.tv.util.images.ImageLoader; import java.util.List; public class ProgramGuideSearchFragment extends SearchFragment { @@ -136,8 +136,9 @@ public class ProgramGuideSearchFragment extends SearchFragment { LocalSearchProvider.SearchResult result = (LocalSearchProvider.SearchResult) o; mMainActivity.getFragmentManager().popBackStack(); mMainActivity.tuneToChannel( - mMainActivity.getChannelDataManager().getChannel( - result.getChannelId())); + mMainActivity + .getChannelDataManager() + .getChannel(result.getChannelId())); } }; diff --git a/src/com/android/tv/setup/SystemSetupActivity.java b/src/com/android/tv/setup/SystemSetupActivity.java index 3fefc0c8..c6b10e52 100644 --- a/src/com/android/tv/setup/SystemSetupActivity.java +++ b/src/com/android/tv/setup/SystemSetupActivity.java @@ -27,6 +27,7 @@ import android.widget.Toast; import com.android.tv.R; import com.android.tv.SetupPassthroughActivity; import com.android.tv.TvSingletons; +import com.android.tv.common.CommonConstants; import com.android.tv.common.ui.setup.SetupActivity; import com.android.tv.common.ui.setup.SetupMultiPaneFragment; import com.android.tv.common.util.CommonUtils; @@ -37,7 +38,8 @@ import com.android.tv.util.TvInputManagerHelper; /** A activity to start input sources setup fragment for initial setup flow. */ public class SystemSetupActivity extends SetupActivity { - private static final String SYSTEM_SETUP = "com.android.tv.action.LAUNCH_SYSTEM_SETUP"; + private static final String SYSTEM_SETUP = + CommonConstants.BASE_PACKAGE + ".action.LAUNCH_SYSTEM_SETUP"; private static final int SHOW_RIPPLE_DURATION_MS = 266; private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1; diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java index df487bb5..c3b8173f 100644 --- a/src/com/android/tv/ui/ChannelBannerView.java +++ b/src/com/android/tv/ui/ChannelBannerView.java @@ -50,17 +50,17 @@ import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; -import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.StreamInfo; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.parental.ContentRatingsManager; -import com.android.tv.util.ImageCache; -import com.android.tv.util.ImageLoader; -import com.android.tv.util.ImageLoader.ImageLoaderCallback; -import com.android.tv.util.ImageLoader.LoadTvInputLogoTask; import com.android.tv.util.Utils; +import com.android.tv.util.images.ImageCache; +import com.android.tv.util.images.ImageLoader; +import com.android.tv.util.images.ImageLoader.ImageLoaderCallback; +import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask; /** A view to render channel banner. */ public class ChannelBannerView extends FrameLayout implements TvTransitionManager.TransitionLayout { @@ -500,7 +500,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage return new ImageLoaderCallback<ChannelBannerView>(channelBannerView) { @Override public void onBitmapLoaded(ChannelBannerView view, @Nullable Bitmap logo) { - if (channel != view.mCurrentChannel) { + if (channel.equals(view.mCurrentChannel)) { // The logo is obsolete. return; } @@ -579,7 +579,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage return; } updateProgramTextView( - program == mLockedChannelProgram, + program.equals(mLockedChannelProgram), program.getTitle(), program.getEpisodeDisplayTitle(getContext())); } diff --git a/src/com/android/tv/ui/InputBannerView.java b/src/com/android/tv/ui/InputBannerView.java index 17bfcab7..5ac715bf 100644 --- a/src/com/android/tv/ui/InputBannerView.java +++ b/src/com/android/tv/ui/InputBannerView.java @@ -25,7 +25,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; public class InputBannerView extends LinearLayout implements TvTransitionManager.TransitionLayout { private final long mShowDurationMillis; diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java index 5919dbf1..e2625811 100644 --- a/src/com/android/tv/ui/KeypadChannelSwitchView.java +++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java @@ -41,8 +41,8 @@ import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import com.android.tv.common.SoftPreconditions; import com.android.tv.common.util.DurationTimer; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelNumber; +import com.android.tv.data.api.Channel; import java.util.ArrayList; import java.util.List; diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java index 2ec498a8..f4949f08 100644 --- a/src/com/android/tv/ui/SelectInputView.java +++ b/src/com/android/tv/ui/SelectInputView.java @@ -36,7 +36,7 @@ import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.analytics.Tracker; import com.android.tv.common.util.DurationTimer; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.util.TvInputManagerHelper; import java.util.ArrayList; import java.util.Collections; diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java index f1e72afb..9c75bc80 100644 --- a/src/com/android/tv/ui/TunableTvView.java +++ b/src/com/android/tv/ui/TunableTvView.java @@ -70,23 +70,24 @@ import com.android.tv.common.util.CommonUtils; import com.android.tv.common.util.Debug; import com.android.tv.common.util.DurationTimer; import com.android.tv.common.util.PermissionUtils; -import com.android.tv.data.Channel; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; import com.android.tv.data.StreamInfo; import com.android.tv.data.WatchedHistoryManager; +import com.android.tv.data.api.Channel; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.recommendation.NotificationService; -import com.android.tv.util.ImageLoader; import com.android.tv.util.NetworkUtils; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; +import com.android.tv.util.images.ImageLoader; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; -public class TunableTvView extends FrameLayout implements StreamInfo { +/** Includes the real {@link AppLayerTvView} handling tuning, block and other display events. */ +public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvViewPlayingApi { private static final boolean DEBUG = false; private static final String TAG = "TunableTvView"; @@ -594,6 +595,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mIsUnderShrunken = isUnderShrunken; } + @Override public boolean isPlaying() { return mStarted; } @@ -706,6 +708,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { mCurrentChannel = currentChannel; } + @Override public void setStreamVolume(float volume) { if (!mStarted) { throw new IllegalStateException("TvView isn't started"); @@ -1260,6 +1263,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * * @param listener The instance of {@link TimeShiftListener}. */ + @Override public void setTimeShiftListener(TimeShiftListener listener) { mTimeShiftListener = listener; } @@ -1295,11 +1299,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** Returns if the time shift is available for the current channel. */ + @Override public boolean isTimeShiftAvailable() { return mTimeShiftAvailable; } /** Plays the media, if the current input supports time-shifting. */ + @Override public void timeshiftPlay() { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); @@ -1311,6 +1317,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** Pauses the media, if the current input supports time-shifting. */ + @Override public void timeshiftPause() { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); @@ -1326,6 +1333,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * * @param speed The speed to rewind the media. e.g. 2 for 2x, 3 for 3x and 4 for 4x. */ + @Override public void timeshiftRewind(int speed) { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); @@ -1345,6 +1353,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * * @param speed The speed to forward the media. e.g. 2 for 2x, 3 for 3x and 4 for 4x. */ + @Override public void timeshiftFastForward(int speed) { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); @@ -1364,6 +1373,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { * * @param timeMs The time in milliseconds to seek to. */ + @Override public void timeshiftSeekTo(long timeMs) { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); @@ -1372,6 +1382,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo { } /** Returns the current playback position in milliseconds. */ + @Override public long timeshiftGetCurrentPositionMs() { if (!isTimeShiftAvailable()) { throw new IllegalStateException("Time-shift is not supported for the current channel"); @@ -1405,21 +1416,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo { }; } - /** Used to receive the time-shift events. */ - public abstract static class TimeShiftListener { - /** - * Called when the availability of the time-shift for the current channel has been changed. - * It should be guaranteed that this is called only when the availability is really changed. - */ - public abstract void onAvailabilityChanged(); - - /** - * Called when the record start time has been changed. This is not called when the recorded - * programs is played. - */ - public abstract void onRecordStartTimeChanged(long recordStartTimeMs); - } - /** A listener which receives the notification when the screen is blocked/unblocked. */ public abstract static class OnScreenBlockingChangedListener { /** Called when the screen is blocked/unblocked. */ diff --git a/src/com/android/tv/ui/TunableTvViewPlayingApi.java b/src/com/android/tv/ui/TunableTvViewPlayingApi.java new file mode 100644 index 00000000..3f19b61f --- /dev/null +++ b/src/com/android/tv/ui/TunableTvViewPlayingApi.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.tv.ui; + +/** API to play pause and set the volume of a TunableTvView */ +public interface TunableTvViewPlayingApi { + + boolean isPlaying(); + + void setStreamVolume(float volume); + + void setTimeShiftListener(TimeShiftListener listener); + + boolean isTimeShiftAvailable(); + + void timeshiftPlay(); + + void timeshiftPause(); + + void timeshiftRewind(int speed); + + void timeshiftFastForward(int speed); + + void timeshiftSeekTo(long timeMs); + + long timeshiftGetCurrentPositionMs(); + + /** Used to receive the time-shift events. */ + abstract class TimeShiftListener { + /** + * Called when the availability of the time-shift for the current channel has been changed. + * It should be guaranteed that this is called only when the availability is really changed. + */ + public abstract void onAvailabilityChanged(); + + /** + * Called when the record start time has been changed. This is not called when the recorded + * programs is played. + */ + public abstract void onRecordStartTimeChanged(long recordStartTimeMs); + } +} diff --git a/src/com/android/tv/ui/TvTransitionManager.java b/src/com/android/tv/ui/TvTransitionManager.java index e3f72dc1..5af3e6f2 100644 --- a/src/com/android/tv/ui/TvTransitionManager.java +++ b/src/com/android/tv/ui/TvTransitionManager.java @@ -32,7 +32,7 @@ import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java index 07f7119f..2726839c 100644 --- a/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java +++ b/src/com/android/tv/ui/sidepanel/ChannelCheckItem.java @@ -20,12 +20,12 @@ import android.text.TextUtils; import android.view.View; import android.widget.TextView; import com.android.tv.R; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; import com.android.tv.data.ChannelDataManager.ChannelListener; import com.android.tv.data.OnCurrentProgramUpdatedListener; import com.android.tv.data.Program; import com.android.tv.data.ProgramDataManager; +import com.android.tv.data.api.Channel; public abstract class ChannelCheckItem extends CompoundButtonItem { private final ChannelDataManager mChannelDataManager; diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java index 8660c830..48b80723 100644 --- a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java +++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java @@ -29,8 +29,9 @@ import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.common.util.SharedPreferencesUtils; -import com.android.tv.data.Channel; +import com.android.tv.data.ChannelImpl; import com.android.tv.data.ChannelNumber; +import com.android.tv.data.api.Channel; import com.android.tv.ui.OnRepeatedKeyInterceptListener; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; @@ -53,7 +54,7 @@ public class CustomizeChannelListFragment extends SideFragment { private static Integer sGroupingType; private TvInputManagerHelper mInputManager; - private Channel.DefaultComparator mChannelComparator; + private ChannelImpl.DefaultComparator mChannelComparator; private boolean mGroupByFragmentRunning; private final List<Item> mItems = new ArrayList<>(); @@ -63,7 +64,7 @@ public class CustomizeChannelListFragment extends SideFragment { super.onCreate(savedInstanceState); mInputManager = getMainActivity().getTvInputManagerHelper(); mInitialChannelId = getMainActivity().getCurrentChannelId(); - mChannelComparator = new Channel.DefaultComparator(getActivity(), mInputManager); + mChannelComparator = new ChannelImpl.DefaultComparator(getActivity(), mInputManager); if (sGroupingType == null) { SharedPreferences sharedPreferences = getContext() diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java index d70cf97a..4e3cf7fb 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ChannelsBlockedFragment.java @@ -30,8 +30,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.android.tv.R; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelNumber; +import com.android.tv.data.api.Channel; import com.android.tv.recommendation.ChannelPreviewUpdater; import com.android.tv.ui.OnRepeatedKeyInterceptListener; import com.android.tv.ui.sidepanel.ActionItem; diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java index 3bd066e0..1c91ba9b 100644 --- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java +++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java @@ -20,7 +20,7 @@ import android.view.View; import android.widget.TextView; import com.android.tv.MainActivity; import com.android.tv.R; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.dialog.PinDialogFragment; import com.android.tv.ui.sidepanel.ActionItem; import com.android.tv.ui.sidepanel.Item; diff --git a/src/com/android/tv/util/AsyncDbTask.java b/src/com/android/tv/util/AsyncDbTask.java index d1ea7aa8..60fa3018 100644 --- a/src/com/android/tv/util/AsyncDbTask.java +++ b/src/com/android/tv/util/AsyncDbTask.java @@ -30,8 +30,9 @@ import android.util.Range; import com.android.tv.TvSingletons; import com.android.tv.common.BuildConfig; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; +import com.android.tv.data.ChannelImpl; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.dvr.data.RecordedProgram; import java.util.ArrayList; import java.util.List; @@ -287,7 +288,7 @@ public abstract class AsyncDbTask<Params, Progress, Result> executor, contentResolver, TvContract.Channels.CONTENT_URI, - Channel.PROJECTION, + ChannelImpl.PROJECTION, null, null, null); @@ -295,7 +296,7 @@ public abstract class AsyncDbTask<Params, Progress, Result> @Override protected final Channel fromCursor(Cursor c) { - return Channel.fromCursor(c); + return ChannelImpl.fromCursor(c); } } diff --git a/src/com/android/tv/util/SetupUtils.java b/src/com/android/tv/util/SetupUtils.java index ad5d5024..0d536320 100644 --- a/src/com/android/tv/util/SetupUtils.java +++ b/src/com/android/tv/util/SetupUtils.java @@ -35,8 +35,8 @@ import android.util.Log; import com.android.tv.TvSingletons; import com.android.tv.common.BaseApplication; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import java.util.Collections; import java.util.HashSet; import java.util.Set; diff --git a/src/com/android/tv/util/TvInputManagerHelper.java b/src/com/android/tv/util/TvInputManagerHelper.java index c4feafb7..625fb7b2 100644 --- a/src/com/android/tv/util/TvInputManagerHelper.java +++ b/src/com/android/tv/util/TvInputManagerHelper.java @@ -36,6 +36,8 @@ import com.android.tv.common.SoftPreconditions; import com.android.tv.common.util.CommonUtils; import com.android.tv.parental.ContentRatingsManager; import com.android.tv.parental.ParentalControlSettings; +import com.android.tv.util.images.ImageCache; +import com.android.tv.util.images.ImageLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -179,7 +181,9 @@ public class TvInputManagerHelper { TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); if (info != null) { mInputMap.put(inputId, info); - mTvInputLabels.put(inputId, info.loadLabel(mContext).toString()); + CharSequence label = info.loadLabel(mContext); + // in tests the label may be missing just use the input id + mTvInputLabels.put(inputId, label != null ? label.toString() : inputId); CharSequence inputCustomLabel = info.loadCustomLabel(mContext); if (inputCustomLabel != null) { mTvInputCustomLabels.put(inputId, inputCustomLabel.toString()); diff --git a/src/com/android/tv/util/Utils.java b/src/com/android/tv/util/Utils.java index 4850811e..a75bd446 100644 --- a/src/com/android/tv/util/Utils.java +++ b/src/com/android/tv/util/Utils.java @@ -43,10 +43,11 @@ import android.view.View; import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.SoftPreconditions; -import com.android.tv.data.Channel; +import com.android.tv.common.util.Clock; import com.android.tv.data.GenreItems; import com.android.tv.data.Program; import com.android.tv.data.StreamInfo; +import com.android.tv.data.api.Channel; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -366,6 +367,32 @@ public class Utils { 0); } + /** + * Returns duration string according to the date & time format. If {@code startUtcMillis} and + * {@code endUtcMills} are equal, formatted time will be returned instead. + * + * @param clock the clock used to get the current time. + * @param startUtcMillis start of duration in millis. Should be less than {code endUtcMillis}. + * @param endUtcMillis end of duration in millis. Should be larger than {@code startUtcMillis}. + * @param useShortFormat {@code true} if abbreviation is needed to save space. In that case, + * date will be omitted if duration starts from today and is less than a day. If it's + * necessary, {@link DateUtils#FORMAT_NUMERIC_DATE} is used otherwise. + */ + public static String getDurationString( + Context context, + Clock clock, + long startUtcMillis, + long endUtcMillis, + boolean useShortFormat) { + return getDurationString( + context, + clock.currentTimeMillis(), + startUtcMillis, + endUtcMillis, + useShortFormat, + 0); + } + @VisibleForTesting static String getDurationString( Context context, @@ -461,13 +488,21 @@ public class Utils { /** Returns the last millisecond of a day which the millis belongs to. */ public static long getLastMillisecondOfDay(long millis) { - Calendar calender = Calendar.getInstance(); - calender.setTime(new Date(millis)); - calender.set(Calendar.HOUR_OF_DAY, 23); - calender.set(Calendar.MINUTE, 59); - calender.set(Calendar.SECOND, 59); - calender.set(Calendar.MILLISECOND, 999); - return calender.getTimeInMillis(); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(millis)); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + return calendar.getTimeInMillis(); + } + + /** Returns the last millisecond of a day which the millis belongs to. */ + public static long getFirstMillisecondOfDay(long millis) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(millis)); + resetCalendar(calendar); + return calendar.getTimeInMillis(); } public static String getAspectRatioString(int width, int height) { @@ -787,7 +822,7 @@ public class Utils { /** Returns the TV input for the given {@code program}. */ @Nullable public static TvInputInfo getTvInputInfoForProgram(Context context, Program program) { - if (!Program.isValid(program)) { + if (!Program.isProgramValid(program)) { return null; } return getTvInputInfoForChannelId(context, program.getChannelId()); diff --git a/src/com/android/tv/util/BitmapUtils.java b/src/com/android/tv/util/images/BitmapUtils.java index 4c67d934..d6bd5a31 100644 --- a/src/com/android/tv/util/BitmapUtils.java +++ b/src/com/android/tv/util/images/BitmapUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.tv.util; +package com.android.tv.util.images; import android.content.ContentResolver; import android.content.Context; diff --git a/src/com/android/tv/util/ImageCache.java b/src/com/android/tv/util/images/ImageCache.java index 1017cb14..e260d67a 100644 --- a/src/com/android/tv/util/ImageCache.java +++ b/src/com/android/tv/util/images/ImageCache.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.tv.util; +package com.android.tv.util.images; import android.support.annotation.VisibleForTesting; import android.util.Log; import android.util.LruCache; -import com.android.tv.common.MemoryManageable; -import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; +import com.android.tv.common.memory.MemoryManageable; +import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo; /** A convenience class for caching bitmap. */ public class ImageCache implements MemoryManageable { diff --git a/src/com/android/tv/util/ImageLoader.java b/src/com/android/tv/util/images/ImageLoader.java index 32ac89f0..e844e2ca 100644 --- a/src/com/android/tv/util/ImageLoader.java +++ b/src/com/android/tv/util/images/ImageLoader.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.tv.util; +package com.android.tv.util.images; import android.content.Context; import android.graphics.Bitmap; @@ -32,7 +32,7 @@ import android.util.ArraySet; import android.util.Log; import com.android.tv.R; import com.android.tv.common.concurrent.NamedThreadFactory; -import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; +import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; diff --git a/tests/common/src/com/android/tv/testing/EpgTestData.java b/tests/common/src/com/android/tv/testing/EpgTestData.java index 87bfb4ed..49a92181 100644 --- a/tests/common/src/com/android/tv/testing/EpgTestData.java +++ b/tests/common/src/com/android/tv/testing/EpgTestData.java @@ -16,9 +16,10 @@ package com.android.tv.testing; -import com.android.tv.data.Channel; +import com.android.tv.data.ChannelImpl; import com.android.tv.data.Lineup; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -173,7 +174,7 @@ public abstract class EpgTestData { } public static Channel toTvChannel(android.support.media.tv.Channel original) { - return new Channel.Builder() + return new ChannelImpl.Builder() .setDisplayName(original.getDisplayName()) .setDisplayNumber(original.getDisplayNumber()) // TODO implement the reset diff --git a/tests/common/src/com/android/tv/testing/FakeEpgReader.java b/tests/common/src/com/android/tv/testing/FakeEpgReader.java index a3505279..710ada55 100644 --- a/tests/common/src/com/android/tv/testing/FakeEpgReader.java +++ b/tests/common/src/com/android/tv/testing/FakeEpgReader.java @@ -19,10 +19,11 @@ package com.android.tv.testing; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Range; -import com.android.tv.data.Channel; +import com.android.tv.data.ChannelImpl; import com.android.tv.data.ChannelNumber; import com.android.tv.data.Lineup; import com.android.tv.data.Program; +import com.android.tv.data.api.Channel; import com.android.tv.data.epg.EpgReader; import com.android.tv.dvr.data.SeriesInfo; import com.google.common.base.Function; @@ -90,7 +91,7 @@ public final class FakeEpgReader implements EpgReader { }, null); if (match != null) { - Channel updatedChannel = new Channel.Builder(match).build(); + ChannelImpl updatedChannel = new ChannelImpl.Builder(match).build(); updatedChannel.setLogoUri(channel.getLogoUri()); result.add(EpgChannel.createEpgChannel(updatedChannel, channel.getDisplayNumber())); } diff --git a/tests/common/src/com/android/tv/testing/TestSingletonApp.java b/tests/common/src/com/android/tv/testing/TestSingletonApp.java index 4f16bd64..f1798352 100644 --- a/tests/common/src/com/android/tv/testing/TestSingletonApp.java +++ b/tests/common/src/com/android/tv/testing/TestSingletonApp.java @@ -20,6 +20,7 @@ import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.media.tv.TvInputManager; import android.os.AsyncTask; import com.android.tv.InputSessionManager; import com.android.tv.MainActivityWrapper; @@ -43,6 +44,7 @@ import com.android.tv.dvr.DvrWatchedPositionManager; import com.android.tv.dvr.recorder.RecordingScheduler; import com.android.tv.perf.PerformanceMonitor; import com.android.tv.perf.StubPerformanceMonitor; +import com.android.tv.testing.testdata.TestData; import com.android.tv.tuner.TunerInputController; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; @@ -59,10 +61,12 @@ public class TestSingletonApp extends Application implements TvSingletons { public FakeTvInputManagerHelper tvInputManagerHelper; public SetupUtils setupUtils; + public DvrManager dvrManager; private final Provider<EpgReader> mEpgReaderProvider = SingletonProvider.create(epgReader); private TunerInputController mTunerInputController; private PerformanceMonitor mPerformanceMonitor; + private ChannelDataManager mChannelDataManager; @Override public void onCreate() { @@ -74,10 +78,19 @@ public class TestSingletonApp extends Application implements TvSingletons { tvInputManagerHelper = new FakeTvInputManagerHelper(this); setupUtils = SetupUtils.createForTvSingletons(this); tvInputManagerHelper.start(); + mChannelDataManager = new ChannelDataManager(this, tvInputManagerHelper); + mChannelDataManager.start(); // HACK reset the singleton for tests BaseApplication.sSingletons = this; } + public void loadTestData(TestData testData, long durationMs) { + tvInputManagerHelper + .getFakeTvInputManager() + .add(testData.getTvInputInfo(), TvInputManager.INPUT_STATE_CONNECTED); + testData.init(this, fakeClock, durationMs); + } + @Override public Analytics getAnalytics() { return null; @@ -88,7 +101,7 @@ public class TestSingletonApp extends Application implements TvSingletons { @Override public ChannelDataManager getChannelDataManager() { - return new ChannelDataManager(this, tvInputManagerHelper); + return mChannelDataManager; } @Override @@ -123,7 +136,7 @@ public class TestSingletonApp extends Application implements TvSingletons { @Override public DvrManager getDvrManager() { - return null; + return dvrManager; } @Override diff --git a/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java b/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java new file mode 100644 index 00000000..3eb97b5f --- /dev/null +++ b/tests/common/src/com/android/tv/testing/robo/RobotTestAppHelper.java @@ -0,0 +1,21 @@ +package com.android.tv.testing.robo; + +import android.media.tv.TvContract; +import com.android.tv.testing.FakeTvProvider; +import com.android.tv.testing.TestSingletonApp; +import com.android.tv.testing.testdata.TestData; +import java.util.concurrent.TimeUnit; +import org.robolectric.Robolectric; + +/** Static utilities for using {@link TestSingletonApp} in roboletric tests. */ +public final class RobotTestAppHelper { + + public static void loadTestData(TestSingletonApp app, TestData testData) { + ContentProviders.register(FakeTvProvider.class, TvContract.AUTHORITY); + app.loadTestData(testData, TimeUnit.DAYS.toMillis(1)); + Robolectric.flushBackgroundThreadScheduler(); + Robolectric.flushForegroundThreadScheduler(); + } + + private RobotTestAppHelper() {} +} diff --git a/tests/common/src/com/android/tv/testing/testdata/TestData.java b/tests/common/src/com/android/tv/testing/testdata/TestData.java index c96a9906..e7e52348 100644 --- a/tests/common/src/com/android/tv/testing/testdata/TestData.java +++ b/tests/common/src/com/android/tv/testing/testdata/TestData.java @@ -17,10 +17,12 @@ package com.android.tv.testing.testdata; import android.content.Context; +import android.media.tv.TvInputInfo; import com.android.tv.common.util.Clock; import com.android.tv.testing.data.ChannelInfo; import com.android.tv.testing.data.ChannelUtils; import com.android.tv.testing.data.ProgramUtils; +import com.android.tv.testing.utils.TestUtils; import java.util.List; /** @@ -33,7 +35,7 @@ import java.util.List; * <li>Channel List * </ul> * - * Call {@link #init(Context)}, update the TvProvider data base with the given values. + * Call {@link #init(Context)}, to update the TvProvider data base with the given values. */ public abstract class TestData { private List<ChannelInfo> channelList; @@ -46,18 +48,39 @@ public abstract class TestData { ProgramUtils.updateProgramForAllChannelsOf(context, getInputId(), clock, durationMs); } - protected abstract String getInputId(); + public abstract TvInputInfo getTvInputInfo(); + + public final String getInputId() { + return getTvInputInfo().getId(); + } public static final TestData DEFAULT_10_CHANNELS = new TestData() { + private TvInputInfo mTvInputInfo = createTvInputInfo(); + + private TvInputInfo createTvInputInfo() { + try { + return TestUtils.createTvInputInfo( + TestUtils.createResolveInfo( + "com.android.tv.testing.testdata", + "com.android.tv.testing.testdata.Default10Channels"), + "com.android.tv.testing.testdata/.Default10Channels", + null, + TvInputInfo.TYPE_TUNER, + true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override protected List<ChannelInfo> createChannels(Context context) { return ChannelUtils.createChannelInfos(context, 10); } @Override - protected String getInputId() { - return "com.android.tv.testing.testdata/.Default10Channels"; + public TvInputInfo getTvInputInfo() { + return mTvInputInfo; } }; } diff --git a/tests/unit/src/com/android/tv/util/TestUtils.java b/tests/common/src/com/android/tv/testing/utils/TestUtils.java index 92669070..6604c9ad 100644 --- a/tests/unit/src/com/android/tv/util/TestUtils.java +++ b/tests/common/src/com/android/tv/testing/utils/TestUtils.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.tv.util; +package com.android.tv.testing.utils; +import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.graphics.drawable.Icon; @@ -186,6 +187,7 @@ public class TestUtils { resolveInfo.serviceInfo.packageName = packageName; resolveInfo.serviceInfo.name = name; resolveInfo.serviceInfo.metaData = new Bundle(); + resolveInfo.serviceInfo.applicationInfo = new ApplicationInfo(); return resolveInfo; } } diff --git a/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java b/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java index 84283d90..795f6370 100644 --- a/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java +++ b/tests/unit/src/com/android/tv/BaseMainActivityTestCase.java @@ -21,8 +21,8 @@ import android.content.Context; import android.os.SystemClock; import android.support.test.rule.ActivityTestRule; import android.text.TextUtils; -import com.android.tv.data.Channel; import com.android.tv.data.ChannelDataManager; +import com.android.tv.data.api.Channel; import com.android.tv.testing.data.ChannelInfo; import com.android.tv.testing.testinput.ChannelStateData; import com.android.tv.testing.testinput.TestInputControlConnection; diff --git a/tests/unit/src/com/android/tv/MainActivityTest.java b/tests/unit/src/com/android/tv/MainActivityTest.java index d28a8cd2..5bd526cc 100644 --- a/tests/unit/src/com/android/tv/MainActivityTest.java +++ b/tests/unit/src/com/android/tv/MainActivityTest.java @@ -23,7 +23,7 @@ import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.widget.TextView; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.testing.testinput.TvTestInputConstants; import com.android.tv.ui.ChannelBannerView; import java.util.List; diff --git a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java index b3d76390..96c1f7a1 100644 --- a/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java +++ b/tests/unit/src/com/android/tv/data/ChannelDataManagerTest.java @@ -40,6 +40,7 @@ import android.test.mock.MockCursor; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; +import com.android.tv.data.api.Channel; import com.android.tv.testing.constants.Constants; import com.android.tv.testing.data.ChannelInfo; import com.android.tv.util.TvInputManagerHelper; diff --git a/tests/unit/src/com/android/tv/data/ChannelTest.java b/tests/unit/src/com/android/tv/data/ChannelImplTest.java index 9b101ed3..b791a7e4 100644 --- a/tests/unit/src/com/android/tv/data/ChannelTest.java +++ b/tests/unit/src/com/android/tv/data/ChannelImplTest.java @@ -27,6 +27,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import com.android.tv.data.api.Channel; import com.android.tv.testing.ComparatorTester; import com.android.tv.util.TvInputManagerHelper; import java.util.Comparator; @@ -38,10 +39,10 @@ import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -/** Tests for {@link Channel}. */ +/** Tests for {@link ChannelImpl}. */ @SmallTest @RunWith(AndroidJUnit4.class) -public class ChannelTest { +public class ChannelImplTest { // Used for testing TV inputs with invalid input package. This could happen when a TV input is // uninstalled while drawing an app link card. private static final String INVALID_TV_INPUT_PACKAGE_NAME = "com.android.tv.invalid_tv_input"; @@ -232,10 +233,11 @@ public class ChannelTest { private void assertAppLinkType( int expectedType, String inputPackageName, String appLinkText, Intent appLinkIntent) { - // In Channel, Intent.URI_INTENT_SCHEME is used to parse the URI. So the same flag should be + // In ChannelImpl, Intent.URI_INTENT_SCHEME is used to parse the URI. So the same flag + // should be // used when the URI is created. - Channel testChannel = - new Channel.Builder() + ChannelImpl testChannel = + new ChannelImpl.Builder() .setPackageName(inputPackageName) .setAppLinkText(appLinkText) .setAppLinkIntentUri( @@ -263,40 +265,40 @@ public class ChannelTest { Comparator<Channel> comparator = new TestChannelComparator(manager); ComparatorTester<Channel> comparatorTester = ComparatorTester.withoutEqualsTest(comparator); comparatorTester.addComparableGroup( - new Channel.Builder().setInputId(PARTNER_INPUT_ID).build()); - comparatorTester.addComparableGroup(new Channel.Builder().setInputId("1").build()); + new ChannelImpl.Builder().setInputId(PARTNER_INPUT_ID).build()); + comparatorTester.addComparableGroup(new ChannelImpl.Builder().setInputId("1").build()); comparatorTester.addComparableGroup( - new Channel.Builder().setInputId("1").setDisplayNumber("2").build()); + new ChannelImpl.Builder().setInputId("1").setDisplayNumber("2").build()); comparatorTester.addComparableGroup( - new Channel.Builder().setInputId("2").setDisplayNumber("1.0").build()); + new ChannelImpl.Builder().setInputId("2").setDisplayNumber("1.0").build()); // display name does not affect comparator comparatorTester.addComparableGroup( - new Channel.Builder() + new ChannelImpl.Builder() .setInputId("2") .setDisplayNumber("1.62") .setDisplayName("test1") .build(), - new Channel.Builder() + new ChannelImpl.Builder() .setInputId("2") .setDisplayNumber("1.62") .setDisplayName("test2") .build(), - new Channel.Builder() + new ChannelImpl.Builder() .setInputId("2") .setDisplayNumber("1.62") .setDisplayName("test3") .build()); comparatorTester.addComparableGroup( - new Channel.Builder().setInputId("2").setDisplayNumber("2.0").build()); + new ChannelImpl.Builder().setInputId("2").setDisplayNumber("2.0").build()); // Numeric display number sorting comparatorTester.addComparableGroup( - new Channel.Builder().setInputId("2").setDisplayNumber("12.2").build()); + new ChannelImpl.Builder().setInputId("2").setDisplayNumber("12.2").build()); comparatorTester.test(); } /** - * Test Input Label handled by {@link com.android.tv.data.Channel.DefaultComparator}. + * Test Input Label handled by {@link ChannelImpl.DefaultComparator}. * * <p>Sort partner inputs first, then sort by input label, then by input id. See <a * href="http://b/23031603">b/23031603</a>. @@ -317,15 +319,15 @@ public class ChannelTest { ComparatorTester<Channel> comparatorTester = ComparatorTester.withoutEqualsTest(comparator); comparatorTester.addComparableGroup( - new Channel.Builder().setInputId(PARTNER_INPUT_ID).setDescription("A").build()); + new ChannelImpl.Builder().setInputId(PARTNER_INPUT_ID).setDescription("A").build()); // The description is used as a label for this test. comparatorTester.addComparableGroup( - new Channel.Builder().setDescription("A").setInputId("1").build()); + new ChannelImpl.Builder().setDescription("A").setInputId("1").build()); comparatorTester.addComparableGroup( - new Channel.Builder().setDescription("A").setInputId("2").build()); + new ChannelImpl.Builder().setDescription("A").setInputId("2").build()); comparatorTester.addComparableGroup( - new Channel.Builder().setDescription("B").setInputId("1").build()); + new ChannelImpl.Builder().setDescription("B").setInputId("1").build()); comparatorTester.test(); } @@ -351,10 +353,10 @@ public class ChannelTest { } private void assertNormalizedDisplayNumber(String displayNumber, String normalized) { - assertThat(Channel.normalizeDisplayNumber(displayNumber)).isEqualTo(normalized); + assertThat(ChannelImpl.normalizeDisplayNumber(displayNumber)).isEqualTo(normalized); } - private static final class TestChannelComparator extends Channel.DefaultComparator { + private static final class TestChannelComparator extends ChannelImpl.DefaultComparator { public TestChannelComparator(TvInputManagerHelper manager) { super(null, manager); } @@ -366,7 +368,7 @@ public class ChannelTest { } private static final class ChannelComparatorWithDescriptionAsLabel - extends Channel.DefaultComparator { + extends ChannelImpl.DefaultComparator { public ChannelComparatorWithDescriptionAsLabel(TvInputManagerHelper manager) { super(null, manager); } diff --git a/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java b/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java index 8bf3efbc..8e892cce 100644 --- a/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java +++ b/tests/unit/src/com/android/tv/data/TvInputNewComparatorTest.java @@ -22,8 +22,8 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.util.Pair; import com.android.tv.testing.ComparatorTester; +import com.android.tv.testing.utils.TestUtils; import com.android.tv.util.SetupUtils; -import com.android.tv.util.TestUtils; import com.android.tv.util.TvInputManagerHelper; import java.util.Comparator; import java.util.LinkedHashMap; diff --git a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java index 539698bf..43bfde09 100644 --- a/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java +++ b/tests/unit/src/com/android/tv/data/WatchedHistoryManagerTest.java @@ -69,7 +69,7 @@ public class WatchedHistoryManagerTest { long fakeId = 100000000; long time = System.currentTimeMillis(); long duration = TimeUnit.MINUTES.toMillis(10); - Channel channel = new Channel.Builder().setId(fakeId).build(); + ChannelImpl channel = new ChannelImpl.Builder().setId(fakeId).build(); mWatchedHistoryManager.logChannelViewStop(channel, time, duration); WatchedRecord record = mWatchedHistoryManager.getRecord(0); @@ -90,7 +90,7 @@ public class WatchedHistoryManagerTest { int size = MAX_HISTORY_SIZE * 2; for (int i = 0; i < size; ++i) { - Channel channel = new Channel.Builder().setId(startChannelId + i).build(); + ChannelImpl channel = new ChannelImpl.Builder().setId(startChannelId + i).build(); mWatchedHistoryManager.logChannelViewStop(channel, time + duration * i, duration); } for (int i = 0; i < MAX_HISTORY_SIZE; ++i) { diff --git a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java index 6e76bf36..f62a5e05 100644 --- a/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java +++ b/tests/unit/src/com/android/tv/recommendation/EvaluatorTestCase.java @@ -20,7 +20,7 @@ import static android.support.test.InstrumentationRegistry.getContext; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper; import com.android.tv.recommendation.Recommender.Evaluator; import com.android.tv.testing.utils.Utils; diff --git a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java index 2fc4bb1c..b929a0ae 100644 --- a/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java +++ b/tests/unit/src/com/android/tv/recommendation/RecommendationUtils.java @@ -17,7 +17,8 @@ package com.android.tv.recommendation; import android.content.Context; -import com.android.tv.data.Channel; +import com.android.tv.data.ChannelImpl; +import com.android.tv.data.api.Channel; import com.android.tv.testing.utils.Utils; import java.util.ArrayList; import java.util.Collection; @@ -106,7 +107,7 @@ public class RecommendationUtils { */ public Channel addChannel() { long channelId = size(); - Channel channel = new Channel.Builder().setId(channelId).build(); + ChannelImpl channel = new ChannelImpl.Builder().setId(channelId).build(); ChannelRecord channelRecord = new ChannelRecord(mContext, channel, false); put(channelId, channelRecord); return channel; diff --git a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java index e2b0b249..1b5cca72 100644 --- a/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java +++ b/tests/unit/src/com/android/tv/recommendation/RecommenderTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; import android.test.MoreAsserts; -import com.android.tv.data.Channel; +import com.android.tv.data.api.Channel; import com.android.tv.recommendation.RecommendationUtils.ChannelRecordSortedMapHelper; import com.android.tv.testing.utils.Utils; import java.util.ArrayList; diff --git a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java index 168e7c6e..6dfed64a 100644 --- a/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java +++ b/tests/unit/src/com/android/tv/util/TvInputManagerHelperTest.java @@ -23,6 +23,7 @@ import android.media.tv.TvInputInfo; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import com.android.tv.testing.ComparatorTester; +import com.android.tv.testing.utils.TestUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/tests/unit/src/com/android/tv/util/ImageCacheTest.java b/tests/unit/src/com/android/tv/util/images/ImageCacheTest.java index cd34895e..b094adc3 100644 --- a/tests/unit/src/com/android/tv/util/ImageCacheTest.java +++ b/tests/unit/src/com/android/tv/util/images/ImageCacheTest.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.android.tv.util; +package com.android.tv.util.images; -import static com.android.tv.util.BitmapUtils.createScaledBitmapInfo; +import static com.android.tv.util.images.BitmapUtils.createScaledBitmapInfo; import static org.junit.Assert.assertSame; import android.graphics.Bitmap; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; -import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; +import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java b/tests/unit/src/com/android/tv/util/images/ScaledBitmapInfoTest.java index 9bb69f80..0bde0981 100644 --- a/tests/unit/src/com/android/tv/util/ScaledBitmapInfoTest.java +++ b/tests/unit/src/com/android/tv/util/images/ScaledBitmapInfoTest.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.tv.util; +package com.android.tv.util.images; import static org.junit.Assert.assertEquals; import android.graphics.Bitmap; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import com.android.tv.util.BitmapUtils.ScaledBitmapInfo; +import com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTuner.java b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTuner.java index c765818d..15e90437 100644 --- a/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTuner.java +++ b/tuner/SampleDvbTuner/src/com/android/tv/tuner/sample/dvb/app/SampleDvbTuner.java @@ -21,7 +21,7 @@ import android.content.Context; import android.content.Intent; import android.media.tv.TvContract; import com.android.tv.common.BaseApplication; -import com.android.tv.common.CommonConstants; +import com.android.tv.common.actions.InputSetupActionUtils; import com.android.tv.common.config.DefaultConfigManager; import com.android.tv.common.config.api.RemoteConfig; import com.android.tv.common.util.CommonUtils; @@ -39,7 +39,7 @@ public class SampleDvbTuner extends BaseApplication { Intent intent = CommonUtils.createSetupIntent( new Intent(context, LiveTvTunerSetupActivity.class), mEmbeddedInputId); - intent.putExtra(CommonConstants.EXTRA_INPUT_ID, mEmbeddedInputId); + intent.putExtra(InputSetupActionUtils.EXTRA_INPUT_ID, mEmbeddedInputId); return intent; } diff --git a/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml index c3192c37..3e6946a9 100644 --- a/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml +++ b/tuner/tests/unittests/javatests/com/android/tv/tuner/layout/tests/AndroidManifest.xml @@ -17,7 +17,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.tv.tuner" android:versionCode="1"> - <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="21"/> + <uses-sdk android:targetSdkVersion="26" android:minSdkVersion="23"/> <application android:label="TunerTvInputLayoutTests" > <activity android:name="com.android.tv.tuner.layout.tests.ScaledLayoutActivity" android:label="ScaledLayout Test" /> |